From afb4175bd53443ee0f63da9e719c567ad804eff7 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 26 May 2026 02:51:34 +0900 Subject: [PATCH] =?UTF-8?q?docs(spec):=20saju-lab=20UI=20v1=20=E2=80=94=20?= =?UTF-8?q?=ED=98=B8=EB=A0=B9=20=EC=82=AC=EC=A3=BC=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=84=A4=EA=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시안 4종(메인/오늘운세/궁합/사주풀이) + 호령 캐릭터 시트 + 컬러시트 기반 - v1 범위: 메인 + 사주풀이 + 오늘운세 (궁합은 v2 placeholder) - 백엔드 확장: fortune_scores + lucky + monthly_flow 산출 - 입력 흐름: reading_id URL 공유 + useSajuReading 캐시 - 데스크탑 우선 + 태블릿 반응형 - CSS .saju-page scope로 격리 + Pretendard + Noto Serif KR 폰트 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../specs/2026-05-26-saju-ui-design.md | 387 ++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-26-saju-ui-design.md diff --git a/docs/superpowers/specs/2026-05-26-saju-ui-design.md b/docs/superpowers/specs/2026-05-26-saju-ui-design.md new file mode 100644 index 0000000..8e8ac4c --- /dev/null +++ b/docs/superpowers/specs/2026-05-26-saju-ui-design.md @@ -0,0 +1,387 @@ +# saju-lab UI v1 — 호령 사주 페이지 설계 + +**작성일**: 2026-05-26 +**상태**: Spec (구현 plan 작성 전) +**전제**: saju-lab 백엔드 완성 (474 tests, SHA 8123f75) + web-ui Task 28 (api helpers + placeholder pages) + +--- + +## 1. 목표 + +사용자 시안 4종(`source/images/saju_page/horyung_saju_main.png`, `_today.png`, `_gunghab.png`, `_saju.png`) + 캐릭터 시트(`source/characters/horyung.png`) + 컬러시트(`saju_color_sheet.png`) 기반으로 web-ui `/saju/*` 페이지를 호령 마스코트와 함께 구축한다. + +v1 범위: **메인 / 오늘의 운세 / 사주풀이** 3개 페이지. 궁합은 v2 placeholder. + +--- + +## 2. 결정된 핵심 사항 + +| 항목 | 결정 | +|------|------| +| 캐릭터 자산 | horyung.png + saju_color_sheet.png에서 PNG 6개 추출 | +| 백엔드 확장 | saju-lab에 fortune_scores + lucky + monthly_flow 산출 추가 | +| 입력 흐름 | 메인에서 사주 1회 입력 → reading_id를 다른 페이지 URL query로 공유 | +| v1 페이지 | 메인 + 사주풀이 + 오늘운세 (궁합은 v2) | +| 반응형 | 데스크탑(1280+) 우선 + 태블릿 그라데이션 | +| 컬러 | 시안 추출 — 크림 베이스 + 다크 네이비 + 골드 + 살구 + 청록 | +| 폰트 | Pretendard (본문) + Noto Serif KR (큰 제목, Google Fonts) | +| CSS 격리 | `.saju-page` scope (다른 페이지에 새지 않음) | + +--- + +## 3. 백엔드 확장 (saju-lab) + +### 3-1. 신설 모듈 + +**`saju-lab/app/calculator/fortune_scores.py`** — 4 카테고리 점수: + +``` +calculate_fortune_scores(saju, analysis, current_year) → { + wealth: 0-100 (재물운) + romance: 0-100 (연애운) + social: 0-100 (인간관계) + career: 0-100 (직장운) + overall: 0-100 (가중평균: wealth*0.3 + career*0.3 + romance*0.2 + social*0.2) +} +``` + +알고리즘 (각 base 60에서 가산/감산, clamp 0-100): + +- **wealth**: +정재 강도 / +편재 강도 / +식상→재 통로 / -비겁 강도 / +세운재성 +- **romance**: +일지 합 / +정관·정재 균형 / -일지 충 / +세운 도화살 +- **social**: +인성 / +비겁 적정 / +식상 / +격국 균형 / +천을귀인 +- **career**: +정관 강도 / +편관 제어 / +일간 신강 / +세운 관성 + +**`saju-lab/app/calculator/lucky.py`** — 럭키 데이터: + +``` +calculate_lucky(saju, analysis, target_date) → { + color: [str, str] # 용신 오행 컬러 1~2개 (예: ["청록", "녹색"]) + number: int 1-9 # (일진 천간 idx + 시진 천간 idx) % 9 + 1 + direction: str # 용신 오행 방향 (동/남/중앙/서/북) + good_signs: [str] # 세운 천간이 일간 재성 → "재물 기회" 등 + warnings: [str] # 세운 지지가 일지 충 → "대인 갈등 주의" +} +``` + +오행→컬러/방향 매핑은 정적 dict. 럭키 숫자는 일진+시진(시간 미상 시 일진만)으로 산출. + +**`saju-lab/app/calculator/monthly_flow.py`** — 12개월 운세 흐름: + +``` +calculate_monthly_flow(saju, year) → [ + {month: 1, stem: "壬", branch: "寅", score: 65, label: "변동"}, + {month: 2, stem: "癸", branch: "卯", score: 70, label: "성장"}, + ... 12 entries +] +``` + +각 월: 해당 월의 60갑자(寅월부터 12월 사이클) → 일간 관계(상생/상극/충/합) → score 0-100 + label(`변동`/`성장`/`안정`/`도전`/`정체` 등). + +### 3-2. `routers/saju.py` 응답 확장 + +`SajuInterpretResponse`에 3 필드 추가: + +```python +fortune_scores: dict # {wealth, romance, social, career, overall} +lucky: dict # {color, number, direction, good_signs, warnings} +monthly_flow: list[dict] # 12 entries +``` + +`interpret_saju_endpoint`에서 계산 + DB 저장 + 응답 포함. + +### 3-3. `db.py` 스키마 마이그레이션 + +`saju_records` 테이블에 ALTER TABLE로 3 컬럼 추가 (idempotent): +- `fortune_scores_json TEXT` +- `lucky_json TEXT` +- `monthly_flow_json TEXT` + +`init_db()`에 try/except OperationalError 패턴 (이미 존재하면 skip). + +`_saju_row_to_dict`에서 3 컬럼 JSON 파싱하여 응답에 포함. + +### 3-4. 테스트 + +- `test_fortune_scores.py` — 5-8 case (정재 강함 → wealth 80+, 일지 충 → romance 50-, clamp 검증) +- `test_lucky.py` — 5 case (오행→컬러/방향 매핑, 럭키 숫자 1-9 범위) +- `test_monthly_flow.py` — 3 case (12 entries 정확, 일간 충 월 score 낮음) + +기존 30 reference fixture 비교는 영향 없음 (응답에 새 필드만 추가). + +--- + +## 4. 프론트엔드 구조 (web-ui) + +### 4-1. 디렉토리 + +``` +web-ui/ +├── public/images/saju/ +│ ├── horyung/ +│ │ ├── horyung-front.png # 시안 main hero용 (정면, 큰 사이즈) +│ │ ├── horyung-bust.png # 작은 카드용 (가슴샷) +│ │ ├── horyung-greeting.png # 인사 표정 (메인 좌상단) +│ │ ├── horyung-thinking.png # 생각하는 표정 (사주풀이) +│ │ ├── horyung-pointing.png # 가르치는 표정 (오늘운세) +│ │ └── horyung-happy.png # 기쁜 표정 (점수 높을 때) +│ ├── frame-cloud.png # 시안의 한국화 산 배경 (hero용) +│ ├── pattern-cloud.svg # 한국 전통 구름 패턴 +│ └── icons/ +│ ├── icon-today.svg +│ ├── icon-heart.svg +│ └── icon-book.svg +└── src/pages/saju/ + ├── Saju.css # 모든 saju 페이지 공통 스타일 (격리) + ├── data/ + │ └── constants.js # 4 카테고리 메타, 컬러 토큰 + ├── hooks/ + │ ├── useSajuForm.js + │ └── useSajuReading.js # reading_id → fetched data + 캐시 + ├── components/ + │ ├── HoryungMascot.jsx + │ ├── SajuNav.jsx # 시안 상단 네비게이션 (호령사주 로고 + nav) + │ ├── SajuInputForm.jsx + │ ├── ActionCard.jsx # 3 카드 (오늘운세/궁합/사주풀이) + │ ├── ScoreCard.jsx # 카테고리 점수 카드 + │ ├── FortuneRing.jsx # 종합점 ring SVG + │ ├── LuckyBox.jsx # 럭키 컬러/숫자/방향 + │ ├── ElementBarChart.jsx # 오행 5색 가로 바 + │ ├── SajuPillars.jsx # 4기둥 8자 표시 + │ ├── MonthlyFlow.jsx # 12개월 운세 흐름 차트 + │ ├── InterpretAccordion.jsx # AI 12항목 아코디언 + │ └── HoryungQuote.jsx # 호령 말풍선 + ├── Saju.jsx # 메인 페이지 + ├── SajuResult.jsx # 사주풀이 결과 + ├── Today.jsx # 오늘의 운세 + └── Compatibility.jsx # v2 placeholder +``` + +### 4-2. 라우팅 (변경 없음, Task 28에서 등록됨) + +| 경로 | 컴포넌트 | reading_id 필요 | +|------|---------|----------------| +| `/saju` | Saju.jsx (메인) | 아니오 | +| `/saju/result?rid=N` | SajuResult.jsx | 예 | +| `/saju/today?rid=N` | Today.jsx | 예 | +| `/saju/compatibility` | Compatibility.jsx (placeholder) | — | + +기존 `/saju/result` 등은 Task 28에서 placeholder로 등록 — 본 task에서 실제 컴포넌트로 교체. + +### 4-3. 데이터 흐름 + +``` +[사용자] → /saju (메인) + ↓ 사주 입력 + ↓ sajuInterpret(form) + ↓ POST /api/saju/interpret +[saju-lab] 계산 + Claude AI + fortune_scores + lucky + monthly_flow + ↓ 응답: { reading_id, ... 풍부한 데이터 } +[프론트] navigate(`/saju/result?rid=${reading_id}`) + +[사주풀이 페이지] /saju/result?rid=N + ↓ useSajuReading(N) → sajuGetReading(N) + ↓ GET /api/saju/readings/N + ↓ saju_data + analysis_data + daeun_data + interpretation_json + fortune_scores + lucky + monthly_flow + ↓ 렌더 + +[오늘운세] /saju/today?rid=N — 사용자가 메인 또는 사주풀이에서 클릭 + ↓ useSajuReading(N) + sajuCurrentFortune(N) + ↓ 렌더: ring + 4 score + lucky + 오늘 세운 +``` + +### 4-4. 호령 마스코트 + +`HoryungMascot.jsx` — `pose` prop으로 6개 PNG 중 선택. + +```jsx + // 메인 좌상단 + // 사주풀이 + // 오늘운세 + // 점수 높을 때 (옵션) +``` + +`onError` 핸들러로 PNG 누락 시 silent (디자인 깨짐 방지). + +### 4-5. CSS 격리 + 컬러 시스템 + +`Saju.css`: + +```css +.saju-page { + /* 베이스 */ + --saju-cream: #FAF6EE; + --saju-paper: #F2EAD8; + --saju-ink: #2E2D45; /* 다크 네이비 (헤더, 본문) */ + --saju-ink-deep: #1F1D38; + + /* 액센트 */ + --saju-gold: #D4A574; + --saju-gold-deep: #B5874E; + --saju-apricot: #C58F76; + --saju-rose: #D9A2A6; + --saju-jade: #4B7065; + --saju-violet: #6A5285; + + /* 카테고리 (3 ActionCard) */ + --saju-today-bg: #4B7065; /* 청록 (오늘운세) */ + --saju-gunghab-bg: #A8736E; /* 살구 (궁합) */ + --saju-saju-bg: #4F4A78; /* 보라 (사주풀이) */ + + /* 점수 카테고리 (4 ScoreCard) */ + --saju-wealth: #D4A574; /* 골드 (재물) */ + --saju-romance: #D9A2A6; /* 로즈 (연애) */ + --saju-social: #4B7065; /* 청록 (인간관계) */ + --saju-career: #6A5285; /* 보라 (직장) */ + + min-height: 100vh; + background: var(--saju-cream); + color: var(--saju-ink); + font-family: 'Pretendard', sans-serif; +} + +.saju-page .saju-h1, +.saju-page .saju-h2 { + font-family: 'Noto Serif KR', serif; + font-weight: 700; + letter-spacing: -0.02em; +} +``` + +모든 saju 컴포넌트의 클래스는 `saju-` prefix로 시작 (다른 페이지와 격리). + +### 4-6. 반응형 + +- 기준: `1280px+` 데스크탑 (시안 그대로) +- `768~1280px` 태블릿: hero 컬럼 → 세로 스택, action card 3 → 2x2 grid +- `~768px` 모바일: 호령 작게 (size="sm"), action card 1열, 입력 폼 세로 + +`@media` 쿼리로 `Saju.css` 안에서 처리. + +### 4-7. 폰트 + +`index.html`에 Google Fonts preconnect + Noto Serif KR 추가: + +```html + + + +``` + +큰 제목(h1/h2)만 Noto Serif KR, 본문은 기존 Pretendard. + +--- + +## 5. 컴포넌트별 세부 + +### 5-1. Saju.jsx (메인) + +레이아웃 (시안 horyung_saju_main.png): +- 상단: SajuNav (호령사주 로고 + 4 nav + "사주풀이 시작하기" 버튼) +- Hero: 좌측 호령(front + greeting 박스) / 우측 큰 h1 + 3 ActionCard +- Bottom: 좌측 통계 미리보기 / 우측 SajuInputForm + +폴백: reading_id가 query에 있으면 (`/saju?rid=N`) 통계 영역에 미리보기 점수 + 마지막 분석 결과로. + +### 5-2. SajuResult.jsx (사주풀이) + +레이아웃 (시안 horyung_saju_saju.png): +- 상단: SajuNav + "사주풀이" 큰 타이틀 + 기본 정보 (이름, 생년월일) + 호령(thinking) +- 중단 좌: 사주 4기둥 표 (SajuPillars) + 오행 바 차트 (ElementBarChart) +- 중단 우: 호령의 비전 박스 (HoryungQuote — interpretation의 summary 발췌) +- 하단: 성격강점 / 직업운 / 재물운 / 연애운 4 카드 (12항목 중 추출) + 12개월 운세 흐름 (MonthlyFlow) +- 우하단: 이번 달 핵심 결정 포인트 (interpretation_json.advice) + +데이터: `useSajuReading(rid)` → saju + analysis + daeun + interpretation_json + monthly_flow + +### 5-3. Today.jsx (오늘의 운세) + +레이아웃 (시안 horyung_saju_today.png): +- 상단: SajuNav + "오늘의 운세" 큰 타이틀 + 호령(pointing) + 풍경 배경 +- 중단: FortuneRing(overall) + 4 ScoreCard(wealth/romance/social/career) + LuckyBox +- 하단: 행운 알림 / 위험 알림 (lucky.good_signs, lucky.warnings) +- 최하단: 다음 페이지 (사주풀이 / 궁합보기) 버튼 + +데이터: `useSajuReading(rid)` → fortune_scores + lucky + `sajuCurrentFortune(rid)` → 오늘 세운 + +### 5-4. Compatibility.jsx (v2 placeholder) + +```jsx +export default function Compatibility() { + return ( +
+ +
+ +

궁합보기는 곧 만나요!

+

두 사람의 사주를 함께 풀어보는 기능을 준비 중입니다.

+ 메인으로 돌아가기 +
+
+ ); +} +``` + +백엔드 `/api/saju/compat/*`는 이미 동작하지만 UI는 v2에서 정식 구현. + +--- + +## 6. 에러 처리 + +| 시나리오 | 처리 | +|---------|------| +| 메인 입력 폼 — 잘못된 날짜 | Pydantic 422 → 폼에서 "올바른 날짜를 입력해주세요" | +| Claude API 504/500 | "잠시 후 다시 시도해주세요" + 사용자 입력 보존 | +| reading_id 무효(404) | "사주 결과를 찾을 수 없습니다" + 메인으로 돌아가기 버튼 | +| 호령 PNG 누락 | onError로 silent hide (디자인은 살짝 빈 자리, 동작은 정상) | +| fortune_scores 산출 실패 (예외) | 기본값 60/60/60/60으로 fallback + 콘솔 warn | + +--- + +## 7. 테스트 전략 + +### 백엔드 +- fortune_scores: 5-8 unit test (각 카테고리 high/low 케이스 + clamp) +- lucky: 5 unit test (오행→컬러 매핑, 숫자 1-9 범위, 방향) +- monthly_flow: 3 unit test (12 entries, 점수 범위, 충/합 영향) +- 기존 30 reference fixture 비교: 영향 없음 (응답 추가 필드만) + +### 프론트 +- 컴포넌트 단위 테스트는 v1 범위 밖 (수동 e2e 검증) +- 로컬 e2e: `npm run dev` + 입력 → 사주풀이/오늘운세 1회 정상 동작 +- 호령 6 PNG 모두 존재 확인 (수동) +- 반응형 — Chrome DevTools 1280/1024/768 3가지 확인 + +--- + +## 8. 위험 + 완화 + +| 위험 | 완화 | +|------|------| +| 호령 PNG crop 좌표가 부정확 | plan 단계에서 PIL로 trial-and-error + 사용자 검수. onError로 silent fallback | +| fortune_scores 점수 산식이 명리학적 부정확 | v1은 plausible default + base 60으로 보수적. 실사용 피드백으로 튜닝 | +| 시안 색상과 미세 차이 | 시안 PNG에서 color picker로 hex 추출 후 CSS variable로 명시 | +| Noto Serif KR Google Fonts 로드 지연 | display=swap로 폰트 fallback (Pretendard) → 깜빡임 최소화 | +| reading_id 만료(DB row 삭제) | 404 graceful fallback + 새 입력 유도 | +| Claude 응답 시간 초과 | nginx timeout 300s + 폼에서 progress 표시 | + +--- + +## 9. 향후 (v2, 본 spec 밖) + +- 궁합보기 페이지 정식 구현 (시안 horyung_saju_gunghab.png 기반) +- 상담안내 페이지 (nav에 있는 메뉴) +- 즐겨찾기/히스토리 페이지 (sajuListReadings 활용) +- 사주풀이 PDF 내보내기 +- 호령 캐릭터 lottie 애니메이션 (정적 PNG → 동적) + +--- + +## 10. 참고 + +- 시안: `source/images/saju_page/horyung_saju_{main,today,gunghab,saju}.png` +- 캐릭터: `source/characters/horyung.png` +- 컬러시트: `source/images/saju_page/saju_color_sheet.png` +- 백엔드: web-backend/saju-lab/ (SHA 8123f75) +- 직전 spec: `docs/superpowers/specs/2026-05-25-saju-tarot-lab-migration-design.md` (saju-lab 백엔드 설계) +- web-ui Task 28 commit: e634cde (api.js + routes + IconSaju + placeholder pages)