feat(phase2.5): 사주 result page 라이트 재스킨 + CLAUDE.md 반영

app/work/saju/result/page.tsx의 히어로 다크 그라디언트/violet 배지, 사이드바
다크 카드, 4기둥 표, 대운 카드를 --jsm 토큰(navy/surface/surface-alt/line/
ink/accent)으로 순수 시각 치환. 서버 로직(사주 계산·hasPaid·로또 구독 조회)과
JSX 구조는 무변경. CLAUDE.md 사주 시스템 섹션에 재스킨 완료 이력 반영.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AAtcmKKtqDUe4NyVgy1aLQ
This commit is contained in:
2026-07-03 10:51:22 +09:00
parent 15825616a3
commit 5ace251b58
2 changed files with 69 additions and 68 deletions

View File

@@ -225,6 +225,7 @@ lib/
## 사주 시스템 (`/app/work/saju`, `/lib/saju-*.ts`)
> **공개 서비스 — 로그인 시 AI 해석 무료(1회/일)**
> 결과 화면 `--jsm` 라이트 재스킨 완료(2026-07-03) — 디자인 가드레일 준수
### AI 연동 (`app/api/saju/analyze/route.ts`)
- **AI**: Google Gemini (`@google/generative-ai`)

View File

@@ -34,7 +34,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
if (isNaN(yearNum) || isNaN(monthNum) || isNaN(dayNum)) {
return (
<div className="min-h-full bg-[#f0f5ff] flex items-center justify-center">
<div className="min-h-full bg-[var(--jsm-bg)] flex items-center justify-center">
<div className="text-center py-20">
<p className="text-slate-500 text-sm mb-4"> . .</p>
<a href="/work/saju/input" className="text-blue-600 underline text-sm"> </a>
@@ -141,16 +141,16 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
const engineBadge = <span className="text-[10px] bg-blue-50 border border-blue-200 text-blue-600 px-2 py-0.5 rounded-full font-semibold">TS </span>;
return (
<div className="min-h-full bg-[#f0f5ff]">
<div className="min-h-full bg-[var(--jsm-bg)]">
{/* 헤더 */}
<div className="bg-gradient-to-br from-[#04102b] via-[#0a1f5c] to-[#04102b] px-6 py-10">
<div className="bg-[var(--jsm-navy)] px-6 py-10">
<div className="max-w-4xl mx-auto text-center">
<div className="inline-flex items-center gap-2 bg-violet-400/10 border border-violet-400/25 text-violet-300 text-xs font-semibold px-4 py-1.5 rounded-full mb-4">
<span className="w-1.5 h-1.5 rounded-full bg-amber-400" />
<div className="inline-flex items-center gap-2 bg-white/10 border border-white/20 text-white text-xs font-semibold px-4 py-1.5 rounded-full mb-4">
<span className="w-1.5 h-1.5 rounded-full bg-[var(--jsm-accent)]" />
</div>
<h1 className="text-3xl font-extrabold text-white mb-2"> </h1>
<p className="text-blue-200/60 text-sm"> AI </p>
<p className="text-white/60 text-sm"> AI </p>
</div>
</div>
@@ -159,16 +159,16 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
{/* 사이드바 */}
<aside className="lg:sticky lg:top-6 h-fit">
<div className="bg-[#04102b] rounded-2xl p-6 text-white">
<div className="bg-[var(--jsm-navy)] rounded-2xl p-6 text-white">
<h2 className="text-base font-bold mb-5 text-center pb-4 border-b border-white/10"> </h2>
<div className="space-y-4 text-sm">
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="text-white/50 mb-1"></div>
<div className="font-bold">
{isLunar ? (
<div>
<div> {inputYear}.{inputMonth}.{inputDay}{isLeap ? ' (윤달)' : ''}</div>
<div className="text-xs text-blue-300/50 mt-0.5"> {yearNum}.{monthNum}.{dayNum}</div>
<div className="text-xs text-white/40 mt-0.5"> {yearNum}.{monthNum}.{dayNum}</div>
</div>
) : (
<div>{yearNum}.{monthNum}.{dayNum}</div>
@@ -177,33 +177,33 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
</div>
{hourNum !== null && (
<div>
<div className="text-blue-300/60 mb-1"> </div>
<div className="text-white/50 mb-1"> </div>
<div className="font-bold">{hourNum}</div>
</div>
)}
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="text-white/50 mb-1"></div>
<div className="font-bold">{gender === 'male' ? '남성' : '여성'}</div>
</div>
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="text-white/50 mb-1"></div>
<div className="font-bold">{zodiacAnimal}</div>
</div>
<div>
<div className="text-blue-300/60 mb-1"> </div>
<div className="font-bold text-amber-300">{solarTermName} </div>
<div className="text-white/50 mb-1"> </div>
<div className="font-bold text-[var(--jsm-accent-soft)]">{solarTermName} </div>
</div>
<div>
<div className="text-blue-300/60 mb-1"></div>
<div className="font-bold text-2xl text-amber-400">
<div className="text-white/50 mb-1"></div>
<div className="font-bold text-2xl text-[var(--jsm-accent-soft)]">
{sajuData.day.stem} ({sajuData.day.stemKr})
</div>
<div className="text-xs text-blue-300/60 mt-1">
<div className="text-xs text-white/50 mt-1">
{FIVE_ELEMENTS_KR[sajuData.day.element as keyof typeof FIVE_ELEMENTS_KR]}({sajuData.day.element})
</div>
</div>
<div className="flex items-center gap-2">
<div className="text-blue-300/60 text-xs"> </div>
<div className="text-white/50 text-xs"> </div>
{engineBadge}
</div>
</div>
@@ -214,7 +214,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
</Link>
<Link href="/work/saju"
className="block w-full text-center bg-violet-500/20 hover:bg-violet-500/30 text-violet-300 px-4 py-2 rounded-lg transition text-sm font-medium">
className="block w-full text-center bg-white/10 hover:bg-white/20 text-[var(--jsm-accent-soft)] px-4 py-2 rounded-lg transition text-sm font-medium">
</Link>
</div>
@@ -225,13 +225,13 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
<main className="space-y-6">
{/* 사주팔자 표 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h2 className="text-xl font-extrabold text-[#04102b] mb-5 text-center"> ()</h2>
<div className="bg-[var(--jsm-surface)] rounded-2xl border border-[var(--jsm-line)] p-6">
<h2 className="text-xl font-extrabold text-[var(--jsm-ink)] mb-5 text-center"> ()</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse text-sm">
<thead>
<tr className="bg-[#04102b] text-white">
<tr className="bg-[var(--jsm-navy)] text-white">
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
{sajuData.hour && <th className="py-2.5 px-3 text-center font-bold text-xs"></th>}
<th className="py-2.5 px-3 text-center font-bold text-xs"></th>
@@ -242,54 +242,54 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
<tbody>
{/* 천간 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
<td className="py-2.5 px-3 text-center font-semibold text-[var(--jsm-ink)] bg-[var(--jsm-surface-alt)] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.hour.stem}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.hour.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.hour.stemKr}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xl font-bold text-[#04102b]">{sajuData.day.stem}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.day.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.day.stemKr}</div>
<div className="text-xs text-amber-600 font-bold mt-0.5"></div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.month.stem}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.month.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.month.stemKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.year.stem}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.year.stem}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.year.stemKr}</div>
</td>
</tr>
{/* 지지 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
<td className="py-2.5 px-3 text-center font-semibold text-[var(--jsm-ink)] bg-[var(--jsm-surface-alt)] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.hour.branch}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.hour.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.hour.branchKr}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xl font-bold text-[#04102b]">{sajuData.day.branch}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.day.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.day.branchKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.month.branch}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.month.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.month.branchKr}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xl font-bold text-[#04102b]">{sajuData.year.branch}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)]">{sajuData.year.branch}</div>
<div className="text-xs text-slate-500 mt-0.5">{sajuData.year.branchKr}</div>
</td>
</tr>
{/* 지장간 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs">
<td className="py-2.5 px-3 text-center font-semibold text-[var(--jsm-ink)] bg-[var(--jsm-surface-alt)] text-xs">
<div></div>
<div className="text-[10px] text-slate-400 font-normal"> </div>
</td>
@@ -320,39 +320,39 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
{/* 십성 */}
<tr className="border-b border-slate-100">
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
<td className="py-2.5 px-3 text-center font-semibold text-[var(--jsm-ink)] bg-[var(--jsm-surface-alt)] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.hour.tenGod}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.hour.tenGod}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xs font-bold text-[#04102b]">{sajuData.day.tenGod}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.day.tenGod}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.month.tenGod}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.month.tenGod}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.year.tenGod}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.year.tenGod}</div>
</td>
</tr>
{/* 십이운성 */}
<tr>
<td className="py-2.5 px-3 text-center font-semibold text-[#04102b] bg-[#f0f5ff] text-xs"></td>
<td className="py-2.5 px-3 text-center font-semibold text-[var(--jsm-ink)] bg-[var(--jsm-surface-alt)] text-xs"></td>
{sajuData.hour && (
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.hour.fortune}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.hour.fortune}</div>
</td>
)}
<td className="py-2.5 px-3 text-center bg-amber-50">
<div className="text-xs font-bold text-[#04102b]">{sajuData.day.fortune}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.day.fortune}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.month.fortune}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.month.fortune}</div>
</td>
<td className="py-2.5 px-3 text-center">
<div className="text-xs font-bold text-[#04102b]">{sajuData.year.fortune}</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{sajuData.year.fortune}</div>
</td>
</tr>
</tbody>
@@ -362,7 +362,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
{/* 지지 상호작용 */}
{branchInteractions.length > 0 && (
<div className="mt-5 pt-5 border-t border-slate-100">
<h3 className="text-sm font-bold text-[#04102b] mb-3 text-center"> </h3>
<h3 className="text-sm font-bold text-[var(--jsm-ink)] mb-3 text-center"> </h3>
<div className="flex flex-wrap justify-center gap-2">
{branchInteractions.map((inter: any, idx: number) => {
const isPositive = inter.type.includes('합');
@@ -386,7 +386,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
{/* 오행 균형 */}
<div className="mt-5 pt-5 border-t border-slate-100">
<h3 className="text-sm font-bold text-[#04102b] mb-4 text-center"> </h3>
<h3 className="text-sm font-bold text-[var(--jsm-ink)] mb-4 text-center"> </h3>
<div className="grid grid-cols-5 gap-2">
{Object.entries(elementScores).map(([element, score]) => (
<div key={element} className="text-center">
@@ -397,13 +397,13 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
<div className="w-full bg-slate-200 rounded-full h-1.5 mb-1">
<div
className={`h-1.5 rounded-full transition-all ${element === sajuData.day.element
? 'bg-gradient-to-r from-[#1a56db] to-[#7c3aed]'
? 'bg-[var(--jsm-accent)]'
: 'bg-slate-400'
}`}
style={{ width: `${Math.max(score, 5)}%` }}
/>
</div>
<div className="text-xs font-bold text-[#04102b]">{score}%</div>
<div className="text-xs font-bold text-[var(--jsm-ink)]">{score}%</div>
</div>
))}
</div>
@@ -414,8 +414,8 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
<div className="grid md:grid-cols-2 gap-6">
{/* 신강/신약 + 용신 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h3 className="text-base font-extrabold text-[#04102b] mb-4"> </h3>
<div className="bg-[var(--jsm-surface)] rounded-2xl border border-[var(--jsm-line)] p-6">
<h3 className="text-base font-extrabold text-[var(--jsm-ink)] mb-4"> </h3>
<div className="flex items-center gap-3 mb-4">
<span className={`inline-block px-4 py-1.5 rounded-xl text-sm font-bold ${
analysis.dayMasterStrength.result === '신강'
@@ -438,7 +438,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
</ul>
<div className="border-t border-slate-100 pt-4">
<h4 className="font-bold text-[#04102b] mb-2.5 text-sm"> / / </h4>
<h4 className="font-bold text-[var(--jsm-ink)] mb-2.5 text-sm"> / / </h4>
<div className="flex flex-wrap gap-2 mb-3">
<span className={`px-2.5 py-1 rounded-lg text-xs font-bold border ${elementBgColors[analysis.yongShin.yongShin] || 'bg-gray-100'}`}>
: {analysis.yongShin.yongShinKr}
@@ -455,17 +455,17 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
</div>
{/* 신살 + 공망 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h3 className="text-base font-extrabold text-[#04102b] mb-4"> ()</h3>
<div className="bg-[var(--jsm-surface)] rounded-2xl border border-[var(--jsm-line)] p-6">
<h3 className="text-base font-extrabold text-[var(--jsm-ink)] mb-4"> ()</h3>
{shinsal.length > 0 ? (
<div className="space-y-2 mb-5">
{shinsal.map((s: any, i: number) => (
<div key={i} className="flex items-start gap-2 p-3 rounded-xl bg-[#f0f5ff]">
<span className="inline-block px-2 py-0.5 bg-[#04102b] text-white rounded-lg text-xs font-bold whitespace-nowrap">
<div key={i} className="flex items-start gap-2 p-3 rounded-xl bg-[var(--jsm-surface-alt)]">
<span className="inline-block px-2 py-0.5 bg-[var(--jsm-navy)] text-white rounded-lg text-xs font-bold whitespace-nowrap">
{s.name}
</span>
<div>
<div className="text-xs font-semibold text-[#04102b]">
<div className="text-xs font-semibold text-[var(--jsm-ink)]">
{s.pillar} {s.branchKr}
</div>
<div className="text-xs text-slate-500 mt-0.5">{s.description}</div>
@@ -478,10 +478,10 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
)}
<div className="border-t border-slate-100 pt-4">
<h4 className="font-bold text-[#04102b] mb-2 text-sm"> ()</h4>
<h4 className="font-bold text-[var(--jsm-ink)] mb-2 text-sm"> ()</h4>
<div className="flex gap-2 mb-2">
{gongmang.branchesKr.map((bk: string, i: number) => (
<span key={i} className="px-2.5 py-1 bg-[#04102b] text-white rounded-lg text-xs font-bold">
<span key={i} className="px-2.5 py-1 bg-[var(--jsm-navy)] text-white rounded-lg text-xs font-bold">
{bk}
</span>
))}
@@ -491,7 +491,7 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
{/* 세운 정보 */}
<div className="border-t border-slate-100 pt-4 mt-4">
<h4 className="font-bold text-[#04102b] mb-2 text-sm">
<h4 className="font-bold text-[var(--jsm-ink)] mb-2 text-sm">
{analysis.seun.year}
</h4>
<div className="flex items-center gap-2 mb-2">
@@ -552,26 +552,26 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
)}
{/* 대운 */}
<div className="bg-white rounded-2xl border border-[#dbe8ff] p-6">
<h2 className="text-lg font-extrabold text-[#04102b] mb-5 text-center">
<div className="bg-[var(--jsm-surface)] rounded-2xl border border-[var(--jsm-line)] p-6">
<h2 className="text-lg font-extrabold text-[var(--jsm-ink)] mb-5 text-center">
() 10
</h2>
{currentDaeun && (
<div className="bg-gradient-to-r from-[#04102b] to-[#0a2060] rounded-2xl p-5 mb-5 text-white">
<h3 className="text-sm font-bold mb-3 text-center text-blue-300"> </h3>
<div className="bg-[var(--jsm-navy)] rounded-2xl p-5 mb-5 text-white">
<h3 className="text-sm font-bold mb-3 text-center text-white/70"> </h3>
<div className="text-center mb-3">
<div className="text-3xl font-bold mb-1">
{currentDaeun.stem}{currentDaeun.branch}
</div>
<div className="text-base text-blue-200">
<div className="text-base text-white/80">
{currentDaeun.stemKr}{currentDaeun.branchKr}
</div>
<div className="text-xs text-blue-300/70 mt-1">
<div className="text-xs text-white/50 mt-1">
{currentDaeun.age} ~ {currentDaeun.age + 9} ({currentDaeun.startYear} ~ {currentDaeun.endYear})
</div>
</div>
<p className="text-center leading-relaxed text-xs text-blue-200/80">
<p className="text-center leading-relaxed text-xs text-white/80">
{getDaeunDescription(currentDaeun, sajuData.day.stem)}
</p>
</div>
@@ -584,15 +584,15 @@ export default async function SajuResultPage({ searchParams }: PageProps) {
daeun.endYear === currentDaeun.endYear;
return (
<div key={index}
className={`rounded-xl p-3 border-2 transition ${isCurrent ? 'bg-amber-50 border-amber-400' : 'bg-white border-[#dbe8ff]'}`}>
className={`rounded-xl p-3 border-2 transition ${isCurrent ? 'bg-amber-50 border-amber-400' : 'bg-[var(--jsm-surface)] border-[var(--jsm-line)]'}`}>
<div className="text-center">
<div className="text-xl font-bold text-[#04102b] mb-0.5">{daeun.stem}{daeun.branch}</div>
<div className="text-xl font-bold text-[var(--jsm-ink)] mb-0.5">{daeun.stem}{daeun.branch}</div>
<div className="text-xs text-slate-500 mb-1.5">{daeun.stemKr}{daeun.branchKr}</div>
<div className="text-xs text-slate-400">{daeun.age} ~ {daeun.age + 9}</div>
<div className="text-xs text-slate-400">{daeun.startYear} ~ {daeun.endYear}</div>
{isCurrent && (
<div className="mt-1.5">
<span className="inline-block bg-[#04102b] text-white text-xs px-2.5 py-0.5 rounded-full font-semibold"></span>
<span className="inline-block bg-[var(--jsm-navy)] text-white text-xs px-2.5 py-0.5 rounded-full font-semibold"></span>
</div>
)}
</div>