Files
web-page-backend/docs/superpowers/specs/2026-05-26-saju-ui-v2-redesign-design.md
gahusb fd40777177 docs(spec): 호령 사주 UI v2 리디자인 — 디자인 시스템 + 4 라우트 동시 교체
백호 사주도사 프로토타입(JSX 11파일 + styles.css)을 web-ui로 옮기는 작업의 spec.
주요 결정: (1) 사주 4 라우트 동시 리디자인 + /saju/me placeholder 신설 (2)
useViewportMode 1024px 분기로 모바일/데스크탑 컴포넌트 분리 (3) BottomNav 5항목
+ DesktopHeader 도입 (4) v1 components 12개 + SajuNav + Saju.css 전체 교체.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 23:24:22 +09:00

416 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 호령 사주 UI v2 리디자인 — 디자인 문서
- **상태**: Spec 단계 (brainstorming 종료, plan 대기)
- **작성일**: 2026-05-26
- **대상 저장소**: `web-ui` (React + Vite, `/saju` 라우트 트리)
- **참조 디자인 소스**: `C:\Users\jaeoh\Desktop\workspace\source\images\saju_page\사주풀이\` (백호 사주도사 프로토타입: babel/standalone JSX 11 파일 + styles.css)
- **선행 시스템**: saju-lab UI v1 (`web-ui/src/pages/saju/`, 호령 캐릭터 7 PNG 자산 포함)
- **백엔드 변경 없음**: saju-lab `/api/saju/*` API는 그대로 사용
---
## 1. 목적 & 성공 기준
### 목적
v1의 임시 구조(컴포넌트 12개 직렬 배치, 단일 SajuNav)를 한국 전통 명리학 미학에 충실한 **풀 디자인 시스템**으로 교체. 4 라우트(`/saju`, `/saju/result`, `/saju/today`, `/saju/compatibility`)를 동시 리디자인하고 신규 `/saju/me` placeholder 추가.
### 성공 기준
1. 4 라우트가 새 디자인 토큰/컴포넌트/네비로 일관되게 동작
2. 1024px breakpoint에서 모바일(BottomNav) ↔ 데스크탑(헤더 nav) 자동 전환
3. `useSajuReading` hook + 기존 API 호출 0개 변경, 응답 매핑만 추가
4. 호령 PNG 7개 자산 100% 재사용 (variant API로 추상화)
5. v1 컴포넌트 12개 + SajuNav 제거 — 두 디자인 시스템 동시 유지 X
6. 시각 QA: 골든 패스(메인→입력→result→today→compatibility) + 1024px ± 경계 + me placeholder 모두 정상
---
## 2. 미학 방향 (Aesthetic Direction)
**컨셉**: *한국 전통 명리학 + 차분한 호령 캐릭터*. 디자인 프로토타입이 이미 강하게 commit한 방향을 충실히 옮긴다.
### 2.1 타이포
- **Display**: Nanum Myeongjo (weight 800, `letter-spacing: -0.02em`) — 페이지 타이틀, h1, 큰 한자
- **Body**: Nanum Gothic (weight 400/700, `letter-spacing: -0.01em`) — 본문, 버튼, 캡션
- **Fallback serif**: Gowun Batang
- Google Fonts CSS 로드는 `web-ui/index.html`에 link 추가 (페이지 import 대신 — preconnect로 LCP 개선)
- Inter/Roboto/system-ui 같은 generic AI sans는 사용 금지
### 2.2 컬러 시스템 (CSS 토큰)
디자인 프로토타입 `styles.css``:root` 변수를 그대로 도입:
| 토큰 | 값 | 용도 |
|---|---|---|
| `--navy` | `#1F2A44` | dominant body color, dark surface |
| `--navy-deep` | `#141B30` | night-bg gradient 하단 |
| `--navy-soft` | `#2E3B5A` | 보조 dark |
| `--ivory` | `#F7F2E8` | paper 배경, dark surface 위 텍스트 |
| `--ivory-soft` | `#FBF7EF` | 카드 배경 |
| `--ivory-warm` | `#F0E9D9` | 액센트 배경 |
| `--gold` | `#D4AF37` | sharp accent, 보더, ornament |
| `--gold-soft` | `#E8C76B` | 활성 상태 텍스트 |
| `--gold-dim` | `#B89530` | 비활성 골드 |
| `--green` / `--green-soft` / `--green-bg` | 한국 전통 녹색 | 궁합 화면 accent |
| `--purple` / `--purple-soft` / `--purple-bg` | `#6A4C7C` 계열 | 사주풀이 accent |
| `--pink` / `--pink-deep` / `--pink-bg` | `#F2C7CD` 계열 | 보조 |
| `--gray` / `--gray-soft` | `#6B6B6B` / `#9A968D` | 메타 텍스트 |
| `--gray-line` / `--gray-line-strong` | 보더 |
| `--shadow-card` / `--shadow-pop` / `--shadow-dark` | 그림자 단계 |
**화면별 accent 단일 색** (팔레트 골고루 분산 안티패턴 회피):
- 홈 (`/saju`) — navy
- 오늘 (`/saju/today`) — gold
- 궁합 (`/saju/compatibility`) — green
- 사주풀이 (`/saju/result`) — purple
- 마이 (`/saju/me`) — gray
### 2.3 배경 텍스처
- `.paper-bg` — radial gold/purple wash + 페이퍼 노이즈 (사주풀이, 오늘, 궁합, 마이)
- `.night-bg` — 밤하늘 gradient (홈 hero)
- `.mt-wash` — 데스크탑 헤더 산수화 SVG decoration (좌하단 + 우하단 산 outline, opacity 0.35)
- 단색 배경은 카드 내부에서만 (`--ivory-soft`)
### 2.4 차별화 요소 (UNFORGETTABLE)
1. **OrnateFrame** — 한국 전통 더블 보더 + 4 코너 꺽쇠 SVG (`<path d="M0 4 L0 0 L4 0" />`)
2. **MascotBubble** — 호령 발자국이 매 말풍선마다 `paw-bob` 2.4s ease infinite로 미세 bobbing
3. **OrnamentBloom** — 골드 꽃봉오리 SVG가 모든 섹션 타이틀 좌우 ornament
4. **TopRibbon** — 구름 SVG ribbon이 페이지 상단에 은은히
5. **CharBox** — 사주명식 천간/지지 한자 Nanum Myeongjo 800 + 원소별 색 (목=green, 화=red, 토=earth, 금=gold, 수=blue)
### 2.5 모션
- `screenIn` 0.3s `cubic-bezier(0.16,1,0.3,1)` translateY(6→0) — 라우트 진입 fade-up
- `paw-bob` 2.4s ease infinite — 호령 발자국
- BottomNav 활성 항목 배경 색 전환 0.2s
- 과한 마이크로 인터랙션 X — "페이지당 1 hero 모션" 원칙
---
## 3. 아키텍처 & 라우팅
### 3.1 라우트 매핑
| 라우트 | 디자인 화면 | 파일 | 상태 |
|---|---|---|---|
| `/saju` | HomeScreen | `Saju.jsx` | **교체** (v1 메인) |
| `/saju/result?rid=N` | SajuScreen (4탭) | `SajuResult.jsx` | **교체** (v1 결과) |
| `/saju/today?rid=N` | TodayScreen | `Today.jsx` | **교체** (v1 오늘) |
| `/saju/compatibility` | MatchScreen | `Compatibility.jsx` | **placeholder → 본격 구현** |
| `/saju/compatibility/result?cid=N` | (디자인에 없음) | `CompatibilityResult.jsx` | 디자인 토큰만 라이트 리스타일 |
| `/saju/me` | MeScreen placeholder | `Me.jsx` | **신규** |
라우트 수: 5 신규 진입점 + 1 sub. `routes.jsx``/saju/me` lazy import 추가.
### 3.2 디렉토리 구조
```
web-ui/src/pages/saju/
├── _shell/ # v2 디자인 시스템 + 네비
│ ├── tokens.css # CSS 변수 정의
│ ├── shell.css # paper-bg, night-bg, mt-wash, OrnateFrame, screenIn
│ ├── useViewportMode.js # 1024px breakpoint hook
│ ├── BottomNav.jsx # 모바일 5항목 (home/today/match/saju/me)
│ ├── DesktopHeader.jsx # 데스크탑 horizontal nav + 로고
│ ├── Mascot.jsx # variant API: full|head|upper|greeting|thinking|pointing|happy
│ ├── MascotBubble.jsx # tone: ivory|navy|purple|green
│ ├── OrnateFrame.jsx
│ ├── OrnamentBloom.jsx
│ ├── TopRibbon.jsx
│ ├── TitleBlock.jsx
│ ├── PrimaryButton.jsx # gold inset shadow
│ ├── GhostButton.jsx
│ ├── Icons.jsx # 5 nav icon + IconPaw/IconChevron/IconSparkle/IconYinYang
│ └── helpers/
│ ├── daeunLabel.js # age → 성장기/학습기/...
│ ├── deriveTraits.js # elements + sipsin → 6 성향
│ └── hexA.js # hex → rgba(x,x,x,a)
├── Saju.jsx # routes 진입, useViewportMode → 분기
├── SajuResult.jsx
├── Today.jsx
├── Compatibility.jsx
├── CompatibilityResult.jsx
├── Me.jsx
└── views/ # mobile/desktop 컴포넌트 분리
├── home.mobile.jsx
├── home.desktop.jsx
├── saju.mobile.jsx # 4탭 (basic/chart/flow/traits)
├── saju.desktop.jsx # 데스크탑은 4탭 그대로 vs 2-column 변형 — plan에서 결정
├── today.mobile.jsx
├── today.desktop.jsx
├── match.mobile.jsx
└── match.desktop.jsx
```
**Me 페이지는 mobile/desktop 분리 안 함** (placeholder라 단순 — `Me.jsx` 본문에 직접 구현).
```
기존 v1 파일들:
- `components/` 디렉토리 **전체 삭제** (SajuNav, HoryungMascot, SajuInputForm, ActionCard, SajuPillars, ElementBarChart, FortuneRing, ScoreCard, LuckyBox, InterpretAccordion, MonthlyFlow, HoryungQuote)
- `hooks/useSajuForm.js`, `hooks/useSajuReading.js` 유지 (데이터 흐름)
- `Saju.css` 신규 `_shell/tokens.css` + `_shell/shell.css`로 교체
---
## 4. 컴포넌트 명세
### 4.1 `useViewportMode()`
```js
function useViewportMode() {
const [mode, setMode] = useState(() =>
typeof window !== 'undefined' && window.innerWidth >= 1024 ? 'desktop' : 'mobile'
);
useEffect(() => {
const onResize = () => {
const next = window.innerWidth >= 1024 ? 'desktop' : 'mobile';
setMode(prev => (prev === next ? prev : next));
};
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return mode;
}
```
- 디자인 프로토타입의 동일 hook 그대로 포팅
- SSR 안전 (typeof window 체크) — Vite 기본 CSR이라 항상 window 존재하지만 방어
- debounce 없음 — resize 빈도가 낮고 setState가 동일 값일 때 reflow 없음 (Object.is 비교)
### 4.2 `<Mascot variant="...">`
| variant | 매핑 PNG (기존 v1 자산) |
|---|---|
| `full` | `/images/saju/horyung/horyung-main.png` |
| `head` | `/images/saju/horyung/horyung-bust.png` (얼굴 중심 crop) |
| `upper` | `/images/saju/horyung/horyung-front.png` |
| `greeting` | `/images/saju/horyung/horyung-greeting.png` |
| `thinking` | `/images/saju/horyung/horyung-thinking.png` |
| `pointing` | `/images/saju/horyung/horyung-pointing.png` |
| `happy` | `/images/saju/horyung/horyung-happy.png` |
props: `variant`, `size` (px), `style` (override). `<img loading="lazy">`.
### 4.3 `<BottomNav current onChange theme>`
- `position: fixed; bottom: 0` — iPhone frame이 아닌 실제 모바일 뷰포트의 하단
- 5 아이템: home/today/match/saju/me. NavLink 사용으로 라우트 매핑 (`useLocation`으로 current 결정)
- theme: `'ivory'` (paper 배경) / `'navy'` (night 배경) — backdrop-filter blur 적용
- 활성 항목: 화면별 accent 색 배경(opacity 0.10~0.18) + 라벨 weight 700
### 4.4 `<DesktopHeader>`
- `position: sticky; top: 0; z-index: 30` — 스크롤 시 상단 고정
- 좌측: 로고 (`壽` 한자 + "호령사주" Nanum Myeongjo)
- 중앙: nav 5 링크 (BottomNav와 동일 항목, horizontal 배치)
- 우측: 미사용 (향후 me 메뉴)
- 배경: `--ivory-soft` + 하단 `--gray-line` 1px
### 4.5 `<OrnateFrame children color bg radius padding double>`
- 디자인 프로토타입 `common.jsx`의 OrnateFrame 그대로 포팅
- `double=true`면 inset 4px 위치에 추가 보더
- 4 코너 꺽쇠 SVG (rotate 0/90/180/270)
### 4.6 `<MascotBubble text align tone tail paw>`
- tone 팔레트 (`ivory`/`navy`/`green`/`purple`) → bg/border/text 색
- `paw=true`면 우하단 IconPaw + `paw-bob` 애니메이션
- `tail=true`면 풍선 꼬리 (rotate 45deg 사각형)
### 4.7 Buttons
- `PrimaryButton`: gold inset shadow (`inset 0 1px 0 rgba(212,175,55,0.4)`) + 풀 너비 옵션
- `GhostButton`: 투명 배경 + 보더만, 동일 폰트/spacing
### 4.8 `Me.jsx` (placeholder, mobile/desktop 공통)
- `paper-bg` + `<TopRibbon>` + `<Mascot variant="thinking">` + `<MascotBubble tone="purple">` "곧 만나요" + 비활성 카드 4개 (이력/북마크/설정/문의 — disabled)
- 뷰포트 분리 없이 단일 컴포넌트 (placeholder라 단순)
### 4.9 입력 폼 컴포넌트 (Home에서 사용)
- `<InputRow label name type ...>` — 디자인 토큰 적용된 단일 행 (label 좌측 64px + input 우측)
- `<DateSelect>`, `<TimeSelect>`, `<GenderToggle>`, `<CalendarToggle>` (양/음력) — `useSajuForm` state와 연결
- Phase 2에서 신설. 기존 v1 `SajuInputForm.jsx`의 검증 로직만 이식, 시각 표현은 새 디자인
---
## 5. 데이터 흐름
### 5.1 hook 재사용
- `useSajuReading(rid)` — 그대로 유지. `api.js``sajuGetReading(id)` 호출 → `reading` 객체 반환
- `useSajuForm()` — 그대로 유지. 입력 검증 + `sajuInterpret(body)` 호출 + navigate
### 5.2 매핑 헬퍼 (`_shell/helpers/`)
#### `daeunLabel(age)` → string
- `age < 10` → "성장기"
- `age < 20` → "학습기"
- `age < 30` → "도전기"
- `age < 40` → "성장기"
- `age < 50` → "전성기"
- `age < 60` → "안정기"
- `age < 70` → "정리기"
- `age >= 70` → "여유기"
#### `deriveTraits(elements, sipsin)` → `[{id, ko, icon, color}]` (최대 6개)
- 강한 원소 1~2개 → 매칭 성향:
- `fire >= 30``{id:'challenge', ko:'도전정신', color:'#C04A4A'}`
- `metal >= 30``{id:'lead', ko:'리더십', color:'#D4AF37'}`
- `wood >= 30``{id:'adapt', ko:'적응력', color:'#4E6B5C'}`
- `water >= 30``{id:'wisdom', ko:'지혜', color:'#3A5A8C'}`
- `earth >= 30``{id:'wealth', ko:'풍부함', color:'#A67B3F'}`
- 일간 강도 (신강/신약) → `will` (의지)
- 결과 6개 미만이면 다음으로 강한 원소 추가
- 순서: 강한 원소 점수 내림차순
#### `hexA(hex, alpha)` → `rgba(...)` 문자열
- 디자인 프로토타입 동일 헬퍼
### 5.3 SAJU_DATA mock → 실제 API 매핑 표
| 디자인 mock 필드 (screen-saju.jsx) | API 응답 경로 (saju-lab) | 비고 |
|---|---|---|
| `name`, `birth`, `gender`, `birthTime`, `birthPlace` | `reading.input.*` | 직접 매핑 |
| `sajuLabel` | `reading.label` | "경오년 신사월 갑자일 OO시" |
| `ilgan` | `reading.ilgan` | `{ko, ch, element, sound}` |
| `pillars[]` | `reading.pillars` | year/month/day/hour 4기둥 |
| `pillars[].cheongan.color` | 원소→색 매핑 (`elementColor()`) | wood=green, fire=red, earth=earth, metal=gold, water=blue |
| `pillars[].sipsin`, `jijang` | `reading.pillars[i].sipsin`, `jijang` | |
| `ohaeng[]` | `reading.analysis.elements` | `{wood, fire, earth, metal, water}``[{id, ko, ch, value, color}]` 변환 |
| `daeun[]` | `reading.daeun` (8개) | `label``daeunLabel(age)` 헬퍼, `current`는 현재 나이 기반 derive |
| `traits[]` | `deriveTraits(elements, sipsin)` | 헬퍼로 derive (API 응답에 직접 없음) |
| TraitsTab `title`, `desc` | 상위 3 성향 → 정적 desc 사전 매핑 | YAGNI: 백엔드에 trait description 추가는 향후 작업 |
| Today: `fortune_scores`, `lucky`, `monthly_flow` | API 응답에 이미 존재 | 그대로 사용 |
### 5.4 BottomNav active state
```jsx
const { pathname } = useLocation();
const current =
pathname === '/saju' ? 'home'
: pathname.startsWith('/saju/today') ? 'today'
: pathname.startsWith('/saju/compatibility') ? 'match'
: pathname.startsWith('/saju/result') ? 'saju'
: pathname.startsWith('/saju/me') ? 'me'
: 'home';
```
---
## 6. 반응형 & 네비게이션 전략
### 6.1 1024px breakpoint
- `< 1024px` → 모바일: 페이지 컴포넌트가 `<MobileXxx>` 렌더, `<BottomNav>` 표시
- `>= 1024px` → 데스크탑: `<DesktopXxx>` 렌더, `<DesktopHeader>` 표시
- 페이지 진입 시 `useViewportMode()`가 결정. resize 시 동적 전환
### 6.2 iPhone frame 제거
- 디자인 프로토타입은 모바일 미리보기용으로 iPhone 외곽선을 그렸으나 실제 모바일 디바이스는 OS frame이 있으므로 frame DOM 제거
- StatusBar(`BrandStatusBar`)도 미사용 — 실제 디바이스 status bar 자연스럽게 사용
### 6.3 컨테이너 max-width
- 모바일: `100%` (BottomNav만 fixed)
- 데스크탑: 콘텐츠 max-width 1200px, `margin: 0 auto`. mt-wash 배경은 viewport 풀
### 6.4 transition between modes
- 1024px 경계에서 mode 변경 시 컴포넌트가 unmount → 새 컴포넌트 mount → screenIn 0.3s 재생
- 폼 입력 중 transition 발생 시: useSajuForm 상태는 hook이 보관하므로 데이터 유실 X
---
## 7. 점진적 구현 단계 (Phase Plan)
각 Phase 끝에 `npm run dev``http://localhost:3007/saju` 시각 확인 + git commit. PR은 Phase 1~3, 4~5, 6 (fixup) 3개로 분할 권장.
| Phase | 산출물 | 검증 |
|---|---|---|
| **1. Shell + 토큰** | `_shell/` 전체 + `Me.jsx` + 라우트 `/saju/me` 추가 + Google Fonts link | `/saju/me` 진입 시 placeholder + BottomNav/Header 모두 정상. 기존 4 페이지 무손상 |
| **2. Home** | `Saju.jsx` + `views/home.{mobile,desktop}.jsx` + 입력 폼 + 호령 hero | 모바일/데스크탑 모두 입력 → submit → `/saju/result?rid=N` 이동 |
| **3. SajuResult** | `SajuResult.jsx` + `views/saju.{mobile,desktop}.jsx` 4탭 + 매핑 헬퍼 | 실제 reading 데이터로 4탭 모두 정상 표시. 일간 표시·오행 막대·대운 흐름·성향 derive 검증 |
| **4. Today** | `Today.jsx` + `views/today.{mobile,desktop}.jsx` | fortune_scores·lucky·monthly_flow 표시. PrimaryButton "다른 운세 보기" → SajuResult 이동 |
| **5. Compatibility** | `Compatibility.jsx` + `views/match.{mobile,desktop}.jsx` 본격 구현. `CompatibilityResult.jsx` 라이트 리스타일 | 두 사람 입력 폼 + compat API 호출 + 결과 화면 |
| **6. QA + cleanup** | v1 `components/` 삭제, `Saju.css` 제거, 시각 QA, 1024px 경계 chrome devtools | 골든 패스 통과, dead code 없음 |
---
## 8. 에러 / 빈 상태
| 상황 | UI |
|---|---|
| API 실패 (네트워크/500) | `<OrnateFrame color="--purple">` + `<MascotBubble tone="purple">` "아이고, 다시 시도해주세요" + `<GhostButton>` 새로고침 |
| `?rid=` 없이 `/saju/result` 직접 진입 | `<MascotBubble tone="ivory">` "사주를 먼저 입력해주세요" + `<PrimaryButton color="--purple">` "사주 입력하러 가기" → `/saju` |
| `?rid=` 없이 `/saju/today` 직접 진입 | 동일 패턴, accent gold |
| `?cid=` 없이 `/saju/compatibility/result` 진입 | 동일 패턴, accent green |
| `/saju/me` | `<MascotBubble tone="purple">` "곧 만나요" + 비활성 placeholder 카드 4개 |
| 백엔드 timeout (사주 해석 30~60초) | 로딩 화면: `<Mascot variant="thinking">` + `<MascotBubble>` "호령이 풀이 중이에요..." + spinner |
---
## 9. 검증 전략
### 9.1 자동 테스트
- `useViewportMode.test.js``vi.mock` window.innerWidth + resize 이벤트 dispatch, 1023/1024 경계 변환 확인
- `daeunLabel.test.js` — 8 구간 모두 정답 매핑
- `deriveTraits.test.js` — 강한 원소 1~5개 입력에 대한 정렬·중복 제거 확인
- `Mascot.test.jsx` — 7 variant 모두 올바른 src prop
### 9.2 시각 검증 (Phase 마다 dev server)
1. `npm run dev``http://localhost:3007/saju` 진입
2. 모바일 chrome devtools (375×667 iPhone SE, 390×844 iPhone 12)
3. 데스크탑 (1280×720 이상)
4. 1024px 경계 ± 1px (1023↔1024)에서 mode 전환 확인
5. 5 라우트 모두 BottomNav active 상태 + DesktopHeader active 상태 일치
6. 호령 PNG 7 variant 모두 로드 확인 (Network 탭)
7. 폰트 로드 (Nanum Myeongjo, Nanum Gothic, Gowun Batang)
### 9.3 회귀
- 기존 reading_id URL 호환 (`/saju/result?rid=N` 패턴 유지)
- `useSajuReading` hook 응답 매핑이 v1과 동일 데이터 표시
- saju-lab API 호출 0개 변경 (네트워크 탭 비교)
---
## 10. YAGNI 명시 제외
다음은 이번 v2에서 의도적으로 **제외**:
- i18n / 다국어
- 다크모드 토글 (디자인 자체가 화면별 light/dark scope 고정)
- 호령 마스코트 드래그·물리 모션 (paw-bob bobbing만)
- BottomNav 햅틱·진동
- 인증/로그인 (Me는 placeholder, 향후 별도 spec)
- PWA / 오프라인 캐시
- 백엔드 trait description API (`deriveTraits` 프론트 헬퍼로 충분)
- 디자인 프로토타입의 desktop-shell.jsx full conversion — DesktopHeader만 차용, shell 전체는 v2 컨테이너에 흡수
---
## 11. 마이그레이션 노트
### 11.1 삭제 대상 (Phase 6에서 일괄 정리)
- `web-ui/src/pages/saju/components/` 전체 12 파일
- `web-ui/src/pages/saju/Saju.css`
- v1 `Compatibility.jsx`의 placeholder 본문 (본격 구현으로 교체)
### 11.2 보존 대상
- `web-ui/src/pages/saju/hooks/useSajuForm.js`, `useSajuReading.js` (데이터 흐름)
- `web-ui/public/images/saju/horyung/` 7 PNG 자산 (Mascot variant API가 매핑)
- `web-ui/src/api.js` saju 헬퍼 함수들
### 11.3 routes.jsx 변경
기존 import 라인 유지 + Me lazy import 추가:
```diff
+ const SajuMe = lazy(() => import('./pages/saju/Me'));
```
`children` 배열에 me 라우트 추가:
```diff
path: '/saju',
children: [
{ index: true, element: <Saju /> },
{ path: 'result', element: <SajuResult /> },
{ path: 'today', element: <SajuToday /> },
{ path: 'compatibility', element: <Compatibility /> },
{ path: 'compatibility/result', element: <CompatibilityResult /> },
+ { path: 'me', element: <SajuMe /> },
],
```
---
## 12. Plan 단계로 넘길 결정 사항
다음은 plan 작성 시 구체화:
- 각 view 파일별 line budget (현실적 500~800 라인 예상, 더 크면 sub 컴포넌트 분할)
- 색→원소 매핑 함수 (`elementColor(elementId)`) 위치 — `_shell/helpers/` vs view 안 인라인
- 데스크탑 `saju.desktop.jsx`의 4탭 유지 vs 2-column 변형 (디자인 프로토타입의 `desktop-saju.jsx` 상세 검토 후 결정)
- 데스크탑 헤더의 me 메뉴 (향후 인증 위치 — 현재는 nav 5번째 링크)
- 시각 QA 시 사용자 직접 확인 단계 (Claude가 puppeteer로 자동화하지 않음 — 시각 판단은 사람)
- `<InputRow>` 등 입력 컴포넌트의 상세 props 시그니처