diff --git a/public/images/saju/horyung/horyung-head.png b/public/images/saju/horyung/horyung-head.png new file mode 100644 index 0000000..b1c9c9c Binary files /dev/null and b/public/images/saju/horyung/horyung-head.png differ diff --git a/public/images/saju/horyung/horyung-upper.png b/public/images/saju/horyung/horyung-upper.png new file mode 100644 index 0000000..c5386e5 Binary files /dev/null and b/public/images/saju/horyung/horyung-upper.png differ diff --git a/src/App.css b/src/App.css index a808296..208692b 100644 --- a/src/App.css +++ b/src/App.css @@ -25,6 +25,16 @@ margin-left: var(--sidebar-w); } +.app-shell--immersive { + height: 100vh; + overflow: hidden; + background: #F7F2E8; +} + +.app-content--immersive { + margin-left: 0; +} + /* ── Layout: Top Bar (mobile only) ──────────────────────────────────── */ .app-topbar { @@ -59,6 +69,11 @@ position: relative; } +.site-main--immersive { + padding: 0; + background: #F7F2E8; +} + @media (max-width: 768px) { .site-main { padding: 16px; @@ -491,6 +506,17 @@ overflow: visible; flex: none; } + + .app-shell--immersive { + height: auto; + min-height: 100vh; + overflow: visible; + } + + .site-main--immersive { + padding: 0; + padding-bottom: 0; + } } /* ── Accessibility: Reduced Motion ──────────────────────────────────── */ diff --git a/src/App.jsx b/src/App.jsx index 6193e84..bcf50f8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Outlet } from 'react-router-dom'; +import { Outlet, useLocation } from 'react-router-dom'; import Navbar from './components/Navbar'; import BottomNav from './components/BottomNav'; import PageHeader from './components/PageHeader'; @@ -9,19 +9,21 @@ import './App.css'; function App() { const isMobile = useIsMobile(); + const { pathname } = useLocation(); + const isImmersiveRoute = pathname.startsWith('/saju'); return ( -
- -
-
- +
+ {!isImmersiveRoute && } +
+
+ {!isImmersiveRoute && }
}>
- {isMobile && } + {isMobile && !isImmersiveRoute && }
); } diff --git a/src/pages/saju/CompatibilityResult.jsx b/src/pages/saju/CompatibilityResult.jsx index 721eb5a..ac3c5bc 100644 --- a/src/pages/saju/CompatibilityResult.jsx +++ b/src/pages/saju/CompatibilityResult.jsx @@ -12,6 +12,7 @@ import MascotBubble from './_shell/MascotBubble'; import OrnateFrame from './_shell/OrnateFrame'; import PrimaryButton from './_shell/PrimaryButton'; import GhostButton from './_shell/GhostButton'; +import MatchResultDesktop from './views/match-result.desktop.jsx'; import { compatGetReading } from '../../api'; export default function CompatibilityResult() { @@ -35,9 +36,12 @@ export default function CompatibilityResult() { return (
{mode === 'desktop' && } -
- -
+ {result && mode === 'desktop' ? ( + + ) : ( +
+ +
{!cid && ( <> @@ -100,8 +104,9 @@ export default function CompatibilityResult() { )} )} -
-
+
+
+ )} {mode === 'mobile' && }
); diff --git a/src/pages/saju/Me.jsx b/src/pages/saju/Me.jsx index d3dfb19..b2b7124 100644 --- a/src/pages/saju/Me.jsx +++ b/src/pages/saju/Me.jsx @@ -8,6 +8,10 @@ import TopRibbon from './_shell/TopRibbon'; import Mascot from './_shell/Mascot'; import MascotBubble from './_shell/MascotBubble'; import OrnateFrame from './_shell/OrnateFrame'; +import DesktopHero from './_shell/DesktopHero'; +import DesktopFooter from './_shell/DesktopFooter'; +import PrimaryButton from './_shell/PrimaryButton'; +import { IconPaw } from './_shell/Icons'; const DISABLED_CARDS = [ { title: '내 사주 이력', desc: '저장된 풀이를 한 번에' }, @@ -21,6 +25,31 @@ export default function Me() { return (
{mode === 'desktop' && } + {mode === 'desktop' ? ( +
+ 걱정 마세요!
저와 함께 차근차근
풀어가요.
} + /> +
+
+
+ 전문가와 1:1 맞춤 상담 +
+
+ 사주풀이를 넘어, 인생의 큰 결정 앞에서 길잡이가 필요하실 때
+ 검증된 명리학 전문가가 30분간 깊이 있게 풀어드립니다. +
+ + 상담 신청하기 + +
+
+ + + ) : (
+ )} {mode === 'mobile' && } ); diff --git a/src/pages/saju/SajuResult.jsx b/src/pages/saju/SajuResult.jsx index 853a47d..b868147 100644 --- a/src/pages/saju/SajuResult.jsx +++ b/src/pages/saju/SajuResult.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useSearchParams, Link } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import './_shell/tokens.css'; import './_shell/shell.css'; import useViewportMode from './_shell/useViewportMode'; @@ -8,10 +8,10 @@ import BottomNav from './_shell/BottomNav'; import DesktopHeader from './_shell/DesktopHeader'; import Mascot from './_shell/Mascot'; import MascotBubble from './_shell/MascotBubble'; -import PrimaryButton from './_shell/PrimaryButton'; import GhostButton from './_shell/GhostButton'; import SajuMobile from './views/saju.mobile.jsx'; import SajuDesktop from './views/saju.desktop.jsx'; +import sampleReading from './sampleReading'; export default function SajuResult() { const mode = useViewportMode(); @@ -23,7 +23,10 @@ export default function SajuResult() { return (
{mode === 'desktop' && } - {!rid && } + {!rid && (mode === 'desktop' + ? + : + )} {rid && loading && } {rid && error && } {rid && data && (mode === 'desktop' @@ -35,20 +38,6 @@ export default function SajuResult() { ); } -function EmptyState() { - return ( -
- - - - 사주 입력하러 가기 - -
- ); -} - function LoadingState() { return (
diff --git a/src/pages/saju/Today.jsx b/src/pages/saju/Today.jsx index ce621ce..86cd275 100644 --- a/src/pages/saju/Today.jsx +++ b/src/pages/saju/Today.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { useSearchParams, Link } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import './_shell/tokens.css'; import './_shell/shell.css'; import useViewportMode from './_shell/useViewportMode'; @@ -8,10 +8,10 @@ import BottomNav from './_shell/BottomNav'; import DesktopHeader from './_shell/DesktopHeader'; import Mascot from './_shell/Mascot'; import MascotBubble from './_shell/MascotBubble'; -import PrimaryButton from './_shell/PrimaryButton'; import GhostButton from './_shell/GhostButton'; import TodayMobile from './views/today.mobile.jsx'; import TodayDesktop from './views/today.desktop.jsx'; +import sampleReading from './sampleReading'; export default function Today() { const mode = useViewportMode(); @@ -23,7 +23,10 @@ export default function Today() { return (
{mode === 'desktop' && } - {!rid && } + {!rid && (mode === 'desktop' + ? + : + )} {rid && loading && } {rid && error && } {rid && data && (mode === 'desktop' @@ -35,20 +38,6 @@ export default function Today() { ); } -function EmptyState() { - return ( -
- - - - 사주 입력하러 가기 - -
- ); -} - function LoadingState() { return (
diff --git a/src/pages/saju/_shell/BrandMark.jsx b/src/pages/saju/_shell/BrandMark.jsx new file mode 100644 index 0000000..9b3a86c --- /dev/null +++ b/src/pages/saju/_shell/BrandMark.jsx @@ -0,0 +1,13 @@ +import React from 'react'; + +export default function BrandMark({ size = 36 }) { + return ( + + ); +} diff --git a/src/pages/saju/_shell/DesktopFooter.jsx b/src/pages/saju/_shell/DesktopFooter.jsx new file mode 100644 index 0000000..f91cdc6 --- /dev/null +++ b/src/pages/saju/_shell/DesktopFooter.jsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { IconSparkle, IconSun, IconUser } from './Icons'; + +function ShieldIcon() { + return ( + + + + + ); +} + +const FOOTER_ITEMS = [ + { label: '전통 명리학 기반', desc: '깊이 있는 전통 해석', icon: }, + { label: 'AI 맞춤 인사이트', desc: '데이터 기반 정확도 향상', icon: }, + { label: '1:1 상담 연계', desc: '필요시 전문가 상담 연결', icon: }, + { label: '안전한 개인정보 관리', desc: '철저한 보안과 비식별 처리', icon: }, +]; + +export default function DesktopFooter() { + return ( +
+
+ {FOOTER_ITEMS.map((item) => ( +
+
{item.icon}
+
+
{item.label}
+
{item.desc}
+
+
+ ))} +
+
+ © 2026 호령사주 · BAEKHO SAJU DOSA +
+
+ ); +} diff --git a/src/pages/saju/_shell/DesktopHeader.jsx b/src/pages/saju/_shell/DesktopHeader.jsx index cb01ac1..f9652dc 100644 --- a/src/pages/saju/_shell/DesktopHeader.jsx +++ b/src/pages/saju/_shell/DesktopHeader.jsx @@ -1,9 +1,16 @@ import React from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; -import { NAV_ITEMS } from './BottomNav'; +import BrandMark from './BrandMark'; +import { IconChevron } from './Icons'; + +const NAV_ITEMS = [ + { id: 'today', to: '/saju/today', label: '오늘의 운세' }, + { id: 'match', to: '/saju/compatibility', label: '궁합보기' }, + { id: 'saju', to: '/saju/result', label: '사주풀이' }, + { id: 'me', to: '/saju/me', label: '상담안내' }, +]; function pathToCurrent(pathname) { - if (pathname === '/saju' || pathname === '/saju/') return 'home'; if (pathname.startsWith('/saju/today')) return 'today'; if (pathname.startsWith('/saju/compatibility')) return 'match'; if (pathname.startsWith('/saju/result')) return 'saju'; @@ -18,40 +25,70 @@ export default function DesktopHeader() { return (
-
); } diff --git a/src/pages/saju/_shell/DesktopHero.jsx b/src/pages/saju/_shell/DesktopHero.jsx new file mode 100644 index 0000000..72be8ae --- /dev/null +++ b/src/pages/saju/_shell/DesktopHero.jsx @@ -0,0 +1,68 @@ +import React from 'react'; +import Mascot from './Mascot'; +import OrnamentBloom from './OrnamentBloom'; +import { IconPaw } from './Icons'; + +export default function DesktopHero({ + title, + subtitle, + accent = '#D4AF37', + bubble, + mascotVariant = 'full', +}) { + return ( +
+
+
+
+ + + + + + + + + +
+

{title}

+
{subtitle}
+
+ + {bubble && ( +
+
+ {bubble} +
+
+ +
+
+
+ )} + + +
+
+ ); +} diff --git a/src/pages/saju/_shell/Icons.jsx b/src/pages/saju/_shell/Icons.jsx index 0670bd5..b28d83e 100644 --- a/src/pages/saju/_shell/Icons.jsx +++ b/src/pages/saju/_shell/Icons.jsx @@ -50,6 +50,43 @@ export function IconUser({ size = 20, stroke = 'currentColor', strokeWidth }) { ); } +export function IconStar({ size = 16, filled = true, color = '#D4AF37' }) { + return ( + + + + ); +} + +export function IconMoney({ size = 20, stroke = 'currentColor', strokeWidth }) { + return ( + + + + + ); +} + +export function IconCalendar({ size = 20, stroke = 'currentColor', strokeWidth }) { + return ( + + + + + ); +} + +export function IconClock({ size = 20, stroke = 'currentColor', strokeWidth }) { + return ( + + + + + ); +} + export function IconPaw({ size = 12, color = 'currentColor' }) { return ( diff --git a/src/pages/saju/_shell/Mascot.jsx b/src/pages/saju/_shell/Mascot.jsx index 61a42ea..3687fe7 100644 --- a/src/pages/saju/_shell/Mascot.jsx +++ b/src/pages/saju/_shell/Mascot.jsx @@ -2,8 +2,8 @@ import React from 'react'; const VARIANT_TO_SRC = { full: '/images/saju/horyung/horyung-main.png', - head: '/images/saju/horyung/horyung-bust.png', - upper: '/images/saju/horyung/horyung-front.png', + head: '/images/saju/horyung/horyung-head.png', + upper: '/images/saju/horyung/horyung-upper.png', greeting: '/images/saju/horyung/horyung-greeting.png', thinking: '/images/saju/horyung/horyung-thinking.png', pointing: '/images/saju/horyung/horyung-pointing.png', diff --git a/src/pages/saju/_shell/PanelHeader.jsx b/src/pages/saju/_shell/PanelHeader.jsx new file mode 100644 index 0000000..41eb800 --- /dev/null +++ b/src/pages/saju/_shell/PanelHeader.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import OrnamentBloom from './OrnamentBloom'; + +export default function PanelHeader({ + title, + color = '#1F2A44', + accent = '#D4AF37', + right = null, + icon = null, +}) { + return ( +
+ {icon || } +

{title}

+
+ {right} +
+ ); +} diff --git a/src/pages/saju/_shell/shell.css b/src/pages/saju/_shell/shell.css index 596ff34..40fa1a4 100644 --- a/src/pages/saju/_shell/shell.css +++ b/src/pages/saju/_shell/shell.css @@ -3,6 +3,8 @@ /* paper texture */ .saju-v2 .paper-bg { background: + linear-gradient(rgba(247, 242, 232, 0.86), rgba(251, 247, 239, 0.92)), + url('/images/saju/horyung/background.png') center top / cover no-repeat, radial-gradient(ellipse at top, rgba(212, 175, 55, 0.06), transparent 60%), radial-gradient(ellipse at bottom, rgba(106, 76, 124, 0.04), transparent 60%), linear-gradient(180deg, var(--ivory) 0%, var(--ivory-soft) 100%); @@ -30,6 +32,8 @@ .saju-v2 .mt-wash { position: relative; background: + linear-gradient(rgba(251, 247, 239, 0.82), rgba(244, 236, 219, 0.9)), + url('/images/saju/horyung/background.png') center top / cover no-repeat, radial-gradient(ellipse 70% 50% at 10% 80%, rgba(31, 42, 68, 0.06), transparent 65%), radial-gradient(ellipse 60% 40% at 90% 70%, rgba(31, 42, 68, 0.05), transparent 65%), radial-gradient(ellipse 100% 60% at 50% 100%, rgba(212, 175, 55, 0.04), transparent 70%), @@ -49,6 +53,34 @@ background-image: url("data:image/svg+xml;utf8,"); } +.saju-v2 .k-frame { + position: relative; + background: rgba(251, 247, 239, 0.9); + border: 1px solid rgba(31, 42, 68, 0.10); + border-radius: 14px; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.6) inset, 0 8px 28px rgba(31, 42, 68, 0.05); +} + +.saju-v2 .k-frame::before { + content: ''; + position: absolute; + inset: 6px; + border: 1px solid rgba(212, 175, 55, 0.16); + border-radius: 10px; + pointer-events: none; +} + +.saju-v2 .k-frame.dark { + background: #1F2A44; + border: 1px solid rgba(212, 175, 55, 0.4); + color: #F7F2E8; + box-shadow: 0 1px 0 rgba(212, 175, 55, 0.2) inset, 0 12px 40px rgba(31, 42, 68, 0.2); +} + +.saju-v2 .k-frame.dark::before { + border-color: rgba(212, 175, 55, 0.25); +} + /* screen entry */ @keyframes saju-screen-in { from { transform: translateY(6px); opacity: 0.8; } @@ -76,6 +108,6 @@ @media (min-width: 1024px) { .saju-v2 .page { padding-bottom: 0; - padding-top: var(--desktop-header-h); + padding-top: 0; } } diff --git a/src/pages/saju/sampleReading.js b/src/pages/saju/sampleReading.js new file mode 100644 index 0000000..a2569a9 --- /dev/null +++ b/src/pages/saju/sampleReading.js @@ -0,0 +1,51 @@ +const sampleReading = { + id: null, + name: '홍길동', + birth_year: 1990, + birth_month: 5, + birth_day: 20, + birth_hour: 10, + gender: 'male', + calendar_type: 'solar', + birth_place: '서울특별시', + saju_data: { + year: { stem: '己', stem_kr: '음토', branch: '巳', branch_kr: '사화', ten_god: '정인', fortune: '丙 庚 戊' }, + month: { stem: '丙', stem_kr: '양화', branch: '子', branch_kr: '자수', ten_god: '편관', fortune: '壬 癸' }, + day: { stem: '庚', stem_kr: '양금', branch: '申', branch_kr: '신금', ten_god: '-', fortune: '庚 壬 戊' }, + hour: { stem: '辛', stem_kr: '음금', branch: '巳', branch_kr: '사화', ten_god: '겁재', fortune: '丙 庚 戊' }, + }, + analysis_data: { + element_scores: { '木': 20, '火': 35, '土': 25, '金': 55, '水': 30 }, + day_master_strength: { result: '강함', score: 78, reasons: ['금 기운 우세', '일간 중심 안정'] }, + }, + fortune_scores: { + overall: 78, + wealth: 80, + romance: 70, + social: 75, + career: 82, + }, + lucky: { + color: ['#1F2A44', '#E8C76B', '#6B4423', '#D89098', '#F7F2E8'], + number: 8, + direction: '동쪽', + time: '오전 10시 ~ 12시', + good_signs: ['작은 기회가 큰 흐름으로 이어질 수 있어요.'], + warnings: ['충동적인 결정은 피하고 여유를 가지세요.'], + }, + daeun_data: [ + { age: 0, start_year: 1990, end_year: 1999, stem: '戊', branch: '戌' }, + { age: 10, start_year: 2000, end_year: 2009, stem: '丁', branch: '酉' }, + { age: 20, start_year: 2010, end_year: 2019, stem: '丙', branch: '申' }, + { age: 30, start_year: 2020, end_year: 2029, stem: '乙', branch: '未' }, + { age: 40, start_year: 2030, end_year: 2039, stem: '甲', branch: '午' }, + { age: 50, start_year: 2040, end_year: 2049, stem: '癸', branch: '巳' }, + { age: 60, start_year: 2050, end_year: 2059, stem: '壬', branch: '辰' }, + { age: 70, start_year: 2060, end_year: 2069, stem: '辛', branch: '卯' }, + ], + interpretation_json: { + summary: '당신은 강한 의지와 추진력을 가진 분입니다. 새로운 것을 두려워하지 않고 도전하는 용기가 큰 장점이며, 주변 사람에게 신뢰감을 주는 리더형의 흐름이 보입니다.', + }, +}; + +export default sampleReading; diff --git a/src/pages/saju/views/home.desktop.jsx b/src/pages/saju/views/home.desktop.jsx index 88e271d..66b9d2b 100644 --- a/src/pages/saju/views/home.desktop.jsx +++ b/src/pages/saju/views/home.desktop.jsx @@ -1,24 +1,19 @@ import React from 'react'; import { useNavigate } from 'react-router-dom'; -import TitleBlock from '../_shell/TitleBlock'; import Mascot from '../_shell/Mascot'; -import MascotBubble from '../_shell/MascotBubble'; import OrnateFrame from '../_shell/OrnateFrame'; +import PanelHeader from '../_shell/PanelHeader'; +import DesktopFooter from '../_shell/DesktopFooter'; import PrimaryButton from '../_shell/PrimaryButton'; -import InputRow from '../_shell/InputRow'; -import { IconSparkle, IconChevron, IconSun, IconHeart, IconYinYang } from '../_shell/Icons'; +import { + IconChevron, IconHeart, IconMoney, IconPaw, IconSparkle, IconSun, IconUser, IconYinYang, +} from '../_shell/Icons'; import useSajuForm from '../hooks/useSajuForm'; -const ACTIONS = [ - { to: '/saju/today', icon: IconSun, label: '오늘의 운세', desc: '오늘 한 줄로 보는 운세', color: '#D4AF37' }, - { to: '/saju/compatibility', icon: IconHeart, label: '궁합보기', desc: '두 사람의 만남 풀이', color: '#4E6B5C' }, - { to: '/saju/result', icon: IconYinYang, label: '사주풀이', desc: '내 사주 자세히', color: '#6A4C7C' }, -]; - const inputStyle = { - flex: 1, padding: '8px 10px', border: '1px solid rgba(31,42,68,0.12)', - borderRadius: 8, background: '#FBF7EF', fontSize: 13, color: '#1F2A44', - fontFamily: 'inherit', + flex: 1, padding: '8px 10px', border: '1px solid rgba(247,242,232,0.16)', + borderRadius: 8, background: 'rgba(247,242,232,0.08)', color: '#F7F2E8', + fontSize: 13, fontFamily: 'inherit', }; function pad(n) { return String(n).padStart(2, '0'); } @@ -31,103 +26,233 @@ function timeValue(form) { return `${pad(form.hour)}:00`; } +const FEATURES = [ + { to: '/saju/today', icon: IconSun, title: '오늘의 운세', desc: '오늘의 흐름과 운세를 한눈에 확인하세요.', color: '#D4AF37' }, + { to: '/saju/compatibility', icon: IconHeart, title: '궁합보기', desc: '소중한 인연과 궁합을 확인해 보세요.', color: '#D89098' }, + { to: '/saju/result', icon: IconYinYang, title: '사주풀이', desc: '내 사주의 구조와 운세를 자세히 풀이해 드립니다.', color: '#3A5A8C' }, +]; + export default function HomeDesktop() { const navigate = useNavigate(); const { form, handleChange, handleSubmit, loading, error } = useSajuForm(); const onDate = (e) => { - const v = e.target.value; - if (!v) { handleChange('year', ''); handleChange('month', ''); handleChange('day', ''); return; } - const [y, m, d] = v.split('-'); - handleChange('year', y); - handleChange('month', String(parseInt(m, 10))); - handleChange('day', String(parseInt(d, 10))); + const value = e.target.value; + if (!value) { handleChange('year', ''); handleChange('month', ''); handleChange('day', ''); return; } + const [year, month, day] = value.split('-'); + handleChange('year', year); + handleChange('month', String(parseInt(month, 10))); + handleChange('day', String(parseInt(day, 10))); }; + const onTime = (e) => { - const v = e.target.value; - if (!v) { handleChange('hour', ''); return; } - const [h] = v.split(':'); - handleChange('hour', String(parseInt(h, 10))); + const value = e.target.value; + if (!value) { handleChange('hour', ''); return; } + const [hour] = value.split(':'); + handleChange('hour', String(parseInt(hour, 10))); }; return ( -
-
- -
-
-
- - -
-
- {ACTIONS.map((a) => ( - - ))} +
+
+
+
+
+ 안녕하세요!
저는 호령이에요.
당신의 길을 비춰드릴게요. +
+
- -
-
사주 입력
- - handleChange('name', e.target.value)} - placeholder="홍길동" style={inputStyle} /> - - +
+
+ + 전통 명리학 × AI 인사이트 +
+

+ 호령이 반갑게
+ 맞이하는
+ 오늘의 사주 +

+

+ 오랜 지혜와 AI 분석으로 정확하고 깊이 있는 당신만의 운명을 안내해 드립니다. +

+ +
+ } title="전통 명리학 기반" desc="정통 사주 해석" /> + } title="AI 분석 인사이트" desc="정확한 인사이트" /> + } title="개인정보 보호" desc="안심 서비스" /> +
+
+
+ +
+ {FEATURES.map((feature) => ( + + ))} +
+
+ +
+ + +
+
+
78
+
/100
+
종합운
+
+
+
+ 새로운 기회가 찾아오는 날입니다. +
+

+ 작은 실천이 큰 변화를 만듭니다. 주변의 조언에 귀 기울여 보세요. +

+
+ } label="재물운" value="80" /> + } label="연애운" value="70" /> + } label="건강운" value="75" /> + } label="직장운" value="82" /> +
+
+
+
+ + + +
+ 사주풀이를 시작해 보세요 +
+
+ 정확한 사주 분석을 위해 생년월일시를 입력해 주세요. +
+
+ + handleChange('name', e.target.value)} placeholder="홍길동" style={inputStyle} /> + + - - + + - - - handleChange('gender', e.target.value)} style={inputStyle}> - - - - - {error && ( -
{error}
- )} -
- - {loading ? '호령이 풀이 중...' : '내 사주 보기'} - {!loading && } - -
- - -
-
+ +
+ + + + {error &&
{error}
} +
+ + {loading ? '호령이 풀이 중...' : '사주풀이 시작하기'} + {!loading && } + +
+ + + + +
); } + +function MiniTrust({ icon, title, desc }) { + return ( +
+ {icon} + + {title} + {desc} + +
+ ); +} + +function ScorePill({ icon, label, value }) { + return ( +
+ {icon} + {label} + {value} +
+ ); +} + +function DarkInputRow({ label, children }) { + return ( + + ); +} diff --git a/src/pages/saju/views/match-result.desktop.jsx b/src/pages/saju/views/match-result.desktop.jsx new file mode 100644 index 0000000..38b8054 --- /dev/null +++ b/src/pages/saju/views/match-result.desktop.jsx @@ -0,0 +1,232 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import DesktopHero from '../_shell/DesktopHero'; +import DesktopFooter from '../_shell/DesktopFooter'; +import PanelHeader from '../_shell/PanelHeader'; +import { + IconChevron, IconHeart, IconPaw, IconSparkle, IconSun, IconUser, +} from '../_shell/Icons'; +import hexA from '../_shell/helpers/hexA'; + +export default function MatchResultDesktop({ result }) { + const navigate = useNavigate(); + const interp = result?.interpretation_json || {}; + const score = Math.round(result?.score || interp.score || 86); + const names = { + a: result?.person_a?.name || '나', + b: result?.person_b?.name || '상대방', + }; + const strengths = interp.strengths?.length ? interp.strengths : ['서로에게 긍정적인 영향을 주며 함께 목표를 이루기 좋아요.']; + const challenges = interp.challenges?.length ? interp.challenges : ['감정 표현 방식이 달라 오해가 생길 수 있으니 배려가 필요해요.']; + const summary = interp.summary || '두 분은 서로의 부족한 부분을 채워주며 함께 성장해 나갈 수 있는 좋은 인연이에요. 서로의 다름을 인정하고 존중한다면 더욱 길고 단단한 관계로 발전할 수 있습니다.'; + + return ( +
+ 두 분의 인연을
제가 잘 살펴봤어요!
함께 행복한 길을 걸어가세요.
} + /> + +
+
+ +
+ +
+ +
+ +
+
+
+ + 궁합 점수 + +
+
+ {score} +
+
+ 상위권의 좋은 궁합이에요. +
+
+
+
+
+ +
+ } label="성향 궁합" score={Math.min(100, score + 2)} desc="가치관과 성향이 조화를 이룹니다." /> + } label="대화 궁합" score={Math.max(0, score - 4)} desc="편안한 대화를 나눌 수 있어요." /> + } label="연애 궁합" score={Math.min(100, score + 4)} desc="설렘과 안정감을 함께 줍니다." /> + } label="결혼 궁합" score={Math.max(0, score - 2)} desc="함께 미래를 그리기 좋은 균형입니다." /> +
+
+ +
+
+ +
+ + + +
+
+ 서로를 북돋우는 상생의 기운이 강합니다. +
+
+ +
+ +
{summary}
+
+ 서로에게 따뜻한 빛이 되어주는 인연입니다. +
+
+ +
+ +
+ + + +
+
+
+ +
+ + +
+
+ + +
+ ); +} + +function PersonSummary({ label, name, chipColor, chipBg }) { + return ( +
+ {label} + {name} + 양력 +
+
+ +
+
+ ); +} + +function SubScoreCard({ color, icon, label, score, desc }) { + return ( +
+
+
{icon}
+ {label} +
+
+
+
+
+ {score} +
+
{desc}
+
+ ); +} + +function Circle({ label, sub, color, center }) { + return ( +
+ {label} + {sub} +
+ ); +} + +function SummaryItem({ color, title, desc }) { + return ( +
+
{title[0]}
+
+
{title}
+
{desc}
+
+
+ ); +} + +function buttonPrimary() { + return { + padding: '14px 26px', borderRadius: 999, background: '#1F2A44', color: '#F7F2E8', + border: '1px solid rgba(212,175,55,0.4)', fontSize: 14, fontWeight: 800, + display: 'flex', alignItems: 'center', gap: 8, + }; +} + +function buttonGhost() { + return { + padding: '14px 26px', borderRadius: 999, background: '#FBF7EF', color: '#1F2A44', + border: '1px solid rgba(31,42,68,0.22)', fontSize: 14, fontWeight: 800, + display: 'flex', alignItems: 'center', gap: 8, + }; +} + +function SpeechIcon({ size = 16, stroke = '#3A5A8C' }) { + return ( + + + + ); +} + +function RingIcon({ size = 18, stroke = '#A67B3F' }) { + return ( + + + + + ); +} diff --git a/src/pages/saju/views/match.desktop.jsx b/src/pages/saju/views/match.desktop.jsx index 57f547d..565ff85 100644 --- a/src/pages/saju/views/match.desktop.jsx +++ b/src/pages/saju/views/match.desktop.jsx @@ -1,10 +1,209 @@ import React from 'react'; -import MatchMobile from './match.mobile.jsx'; +import DesktopHero from '../_shell/DesktopHero'; +import DesktopFooter from '../_shell/DesktopFooter'; +import PanelHeader from '../_shell/PanelHeader'; +import PrimaryButton from '../_shell/PrimaryButton'; +import { + IconCalendar, IconClock, IconHeart, IconPaw, IconSparkle, IconUser, +} from '../_shell/Icons'; +import hexA from '../_shell/helpers/hexA'; -export default function MatchDesktop(props) { +function pad(n) { return String(n).padStart(2, '0'); } +function dateValue(person) { + if (!person.year || !person.month || !person.day) return ''; + return `${person.year}-${pad(person.month)}-${pad(person.day)}`; +} +function timeValue(person) { + if (person.hour === '' || person.hour == null) return ''; + return `${pad(person.hour)}:00`; +} +function onDate(person, onChange, event) { + const value = event.target.value; + if (!value) return onChange({ ...person, year: '', month: '', day: '' }); + const [year, month, day] = value.split('-'); + return onChange({ ...person, year: parseInt(year, 10), month: parseInt(month, 10), day: parseInt(day, 10) }); +} +function onTime(person, onChange, event) { + const value = event.target.value; + if (!value) return onChange({ ...person, hour: null }); + const [hour] = value.split(':'); + return onChange({ ...person, hour: parseInt(hour, 10) }); +} + +export default function MatchDesktop({ + personA, personB, onChangeA, onChangeB, onSubmit, loading, error, +}) { return ( -
- +
+ 두 분의 인연을
제가 잘 살펴봐드릴게요!
함께 행복한 길을 걸어가시길 바라요.
} + /> + +
+
+ +
+ +
+ +
+ + {error && ( +
+ {error} +
+ )} + +
+
+ +
+ 두 사람의 사주를 바탕으로 성향, 대화, 연애, 결혼 가능성까지 다양한 측면에서 조화와 흐름을 분석합니다. +
+
+ + {loading ? '호령이 비교 중...' : '궁합보기 시작'} + {!loading && } + +
+
+ + +
+ ); +} + +function PersonCard({ label, chipColor, chipBg, avatarBg, person, onChange }) { + const activeGender = person.gender || 'male'; + return ( +
+
+ {label} + onChange({ ...person, name: event.target.value })} + placeholder="이름" + style={{ + flex: 1, minWidth: 120, padding: '12px 14px', borderRadius: 10, + border: '1px solid rgba(31,42,68,0.12)', background: '#FBF7EF', + color: '#1F2A44', fontSize: 15, fontWeight: 700, + }} + /> + {person.calendar_type === 'lunar' ? '음력' : '양력'} +
+ +
+
+ +
+ }> + onDate(person, onChange, event)} style={fieldInputStyle} /> + + +
+
+ }> + onTime(person, onChange, event)} style={fieldInputStyle} /> + +
+ {[ + ['male', '남'], + ['female', '여'], + ].map(([value, text]) => { + const active = activeGender === value; + return ( + + ); + })} +
+
+
); } + +function FieldPill({ icon, children }) { + return ( +
+ {icon} + {children} +
+ ); +} + +const fieldInputStyle = { + flex: 1, + minWidth: 0, + border: 'none', + background: 'transparent', + color: '#1F2A44', + fontSize: 14, + fontFamily: 'inherit', + padding: '11px 0', +}; + +const selectStyle = { + border: '1px solid rgba(31,42,68,0.12)', + borderRadius: 10, + background: '#FBF7EF', + color: '#1F2A44', + fontSize: 14, + fontFamily: 'inherit', + padding: '0 12px', +}; diff --git a/src/pages/saju/views/saju.desktop.jsx b/src/pages/saju/views/saju.desktop.jsx index 2f47a3c..bb15177 100644 --- a/src/pages/saju/views/saju.desktop.jsx +++ b/src/pages/saju/views/saju.desktop.jsx @@ -1,10 +1,499 @@ import React from 'react'; -import SajuMobile from './saju.mobile.jsx'; +import { useNavigate } from 'react-router-dom'; +import DesktopHero from '../_shell/DesktopHero'; +import DesktopFooter from '../_shell/DesktopFooter'; +import PanelHeader from '../_shell/PanelHeader'; +import OrnamentBloom from '../_shell/OrnamentBloom'; +import { IconChevron, IconPaw } from '../_shell/Icons'; +import deriveTraits from '../_shell/helpers/deriveTraits'; +import daeunLabel from '../_shell/helpers/daeunLabel'; +import hexA from '../_shell/helpers/hexA'; + +const HANJA_TO_ID = { '木': 'wood', '火': 'fire', '土': 'earth', '金': 'metal', '水': 'water' }; +const ID_TO_KO = { wood: '목', fire: '화', earth: '토', metal: '금', water: '수' }; +const ID_TO_CH = { wood: '木', fire: '火', earth: '土', metal: '金', water: '水' }; +const ID_TO_COLOR = { + wood: '#4E6B5C', fire: '#C04A4A', earth: '#A67B3F', + metal: '#D4AF37', water: '#3A5A8C', +}; +const STEM_EL = { '甲': 'wood', '乙': 'wood', '丙': 'fire', '丁': 'fire', '戊': 'earth', '己': 'earth', '庚': 'metal', '辛': 'metal', '壬': 'water', '癸': 'water' }; +const BRANCH_EL = { '子': 'water', '丑': 'earth', '寅': 'wood', '卯': 'wood', '辰': 'earth', '巳': 'fire', '午': 'fire', '未': 'earth', '申': 'metal', '酉': 'metal', '戌': 'earth', '亥': 'water' }; + +const PILLAR_LABELS = { year: '년주', month: '월주', day: '일주', hour: '시주' }; + +function elementsByEngId(scores = {}) { + const out = {}; + for (const [key, value] of Object.entries(scores || {})) { + const id = HANJA_TO_ID[key] || key; + if (ID_TO_KO[id]) out[id] = Number(value) || 0; + } + return out; +} + +function maxElement(elementsObj) { + return ['wood', 'fire', 'earth', 'metal', 'water'] + .map((id) => ({ id, value: Math.round(elementsObj[id] || 0), color: ID_TO_COLOR[id] })) + .reduce((best, item) => (item.value > best.value ? item : best), { id: 'metal', value: 0, color: '#D4AF37' }); +} + +function normalizePillar(pillar = {}, key) { + const stem = pillar.stem || '-'; + const branch = pillar.branch || '-'; + return { + id: key, + label: PILLAR_LABELS[key], + cheongan: { + ch: stem, + ko: pillar.stem_kr || '', + mark: stem && STEM_EL[stem] ? `(${ID_TO_KO[STEM_EL[stem]]})` : '', + color: ID_TO_COLOR[STEM_EL[stem]] || '#1F2A44', + }, + jiji: { + ch: branch, + ko: pillar.branch_kr || '', + mark: branch && BRANCH_EL[branch] ? `(${ID_TO_KO[BRANCH_EL[branch]]})` : '', + color: ID_TO_COLOR[BRANCH_EL[branch]] || '#1F2A44', + }, + sipsin: pillar.ten_god || '-', + jijang: pillar.hidden_stems || pillar.fortune || '-', + }; +} + +function readingToDesktopData(reading) { + const saju = reading?.saju_data || {}; + const elementsObj = elementsByEngId(reading?.analysis_data?.element_scores); + const strongest = maxElement(elementsObj); + const pillars = ['year', 'month', 'day', 'hour'].map((key) => normalizePillar(saju[key], key)); + const daeun = (reading?.daeun_data || []).map((item) => ({ + age: `${item.age}~${item.age + 9}세`, + rawAge: item.age, + gan: item.stem || item.gan || '-', + label: daeunLabel(item.age), + current: item.start_year <= new Date().getFullYear() && new Date().getFullYear() <= item.end_year, + startYear: item.start_year, + endYear: item.end_year, + })); + const fallbackDaeun = [0, 10, 20, 30, 40, 50, 60, 70].map((age, index) => ({ + age: `${age}~${age + 9}세`, + rawAge: age, + gan: ['戊', '丁', '丙', '乙', '甲', '癸', '壬', '辛'][index], + label: daeunLabel(age), + current: age === 30, + })); + + return { + name: reading?.name || '백호', + gender: reading?.gender === 'female' ? '여' : '남', + birth: `${reading?.birth_year || '1990'}년 ${reading?.birth_month || '01'}월 ${reading?.birth_day || '01'}일 ${reading?.birth_hour ?? '10'}:00`, + lunar: reading?.calendar_type === 'lunar' ? '음력 입력' : '양력 입력', + birthPlace: reading?.birth_place || '서울특별시', + ilgan: pillars[2]?.cheongan || { ch: '庚', color: '#3A5A8C' }, + pillars, + elementsObj, + ohaeng: ['wood', 'fire', 'earth', 'metal', 'water'].map((id) => ({ + id, ko: ID_TO_KO[id], ch: ID_TO_CH[id], + value: Math.round(elementsObj[id] || ({ wood: 20, fire: 35, earth: 25, metal: 55, water: 30 }[id])), + color: ID_TO_COLOR[id], + })), + strongest, + summary: reading?.interpretation_json?.summary || '의리가 강하고 책임감이 뛰어난 흐름입니다. 목표를 정하면 끝까지 해내는 추진력과 원칙을 중시하는 태도가 장점으로 드러납니다.', + traits: deriveTraits(elementsObj, []), + daeun: daeun.length ? daeun : fallbackDaeun, + dayMasterStrength: reading?.analysis_data?.day_master_strength, + }; +} export default function SajuDesktop({ reading }) { + const navigate = useNavigate(); + const data = readingToDesktopData(reading); + return ( -
- +
+ 사주의 흐름을 읽고,
당신의 길을 밝혀드립니다.
} + /> + +
+ navigate('/saju')} /> + +
+ + + +
+ +
+ + t.ko || t.label)} /> + + + +
+ + + navigate('/saju/me')} /> +
+ + + + ); +} + +function BasicInfoBar({ data, onEdit }) { + return ( +
+
+
+ + + + +
+
기본 정보
+
+ + + + + + 양 · 일간 {data.ilgan.ch}} /> +
+ +
+ ); +} + +function InfoCol({ label, value }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +function SajuStructureCard({ data }) { + return ( +
+ + + + + + ))} + + + + pillar.cheongan)} day /> + pillar.jiji)} day /> + pillar.sipsin)} /> + pillar.jijang)} mono /> + +
+ {data.pillars.map((pillar) => ( + + {pillar.id === 'day' && ( +
일간
+ )} +
{pillar.label}
+
+
+ ※ 일간(나)을 중심으로 사주의 흐름과 균형을 해석합니다. +
+
+ ); +} + +const thStyle = ({ active = false } = {}) => ({ + padding: '8px 4px 12px', + textAlign: 'center', + borderBottom: '1px solid rgba(31,42,68,0.08)', + background: active ? 'rgba(106,76,124,0.06)' : 'transparent', + position: 'relative', +}); + +function Row({ label, cells, day }) { + return ( + + {label} + {cells.map((cell, index) => { + const isDay = index === 2 && day; + return ( + +
{cell.ch}
+
+ {cell.ko} {cell.mark} +
+ + ); + })} + + ); +} + +function RowText({ label, cells, mono }) { + return ( + + {label} + {cells.map((cell, index) => ( + {String(cell || '-')} + ))} + + ); +} + +function OhaengCard({ data }) { + const strongest = maxElement(data.elementsObj); + return ( +
+ +
+ {data.ohaeng.map((element) => { + const height = Math.min(100, Math.max(8, (element.value / 60) * 100)); + return ( +
+
+
{element.value}%
+
+
+
+ {element.ko}({element.ch}) +
+
+ ); + })} +
+
+
+ {ID_TO_KO[strongest.id]}({ID_TO_CH[strongest.id]})의 기운이 강한 사주입니다. +
+
+ 강한 기운을 바탕으로 장점을 살리고 부족한 기운은 생활 습관과 관계에서 보완해 보세요. +
+
+
+ ); +} + +function HoryungInsightCard({ data }) { + const strongest = maxElement(data.elementsObj); + const items = [ + { title: `일간이 ${data.ilgan.ch}이시네요.`, desc: '단단한 중심과 자기 기준을 갖고 흐름을 읽는 힘이 있습니다.' }, + { title: `${ID_TO_KO[strongest.id]}(${ID_TO_CH[strongest.id]})의 기운이 두드러져요.`, desc: '해당 기운의 장점을 생활과 일의 방향으로 살려보세요.' }, + { title: '균형을 보완하면 더욱 좋아요.', desc: '강한 기운만 밀어붙이기보다 부족한 기운을 의식하면 흐름이 부드러워집니다.' }, + { title: '지금의 선택이 미래의 나를 만듭니다.', desc: '작은 실천을 꾸준히 쌓는 시기로 삼아보세요.' }, + ]; + return ( +
+
+ +

호령이의 해설

+ +
+
+ {items.map((item, index) => ( +
+
{index + 1}
+
+
{item.title}
+
{item.desc}
+
+
+ ))} +
+
+
+ 지금의 선택이
미래의 나를 만듭니다. + +
+
+
+ ); +} + +function TraitDeskCard({ color, iconName, title, body, bullets }) { + return ( +
+
+
+ +
+
{title}
+
+ {body &&
{body}
} + {bullets && ( +
    + {bullets.map((item) => ( +
  • + · {item} +
  • + ))} +
+ )} +
+ ); +} + +function TraitIcon({ name, color, size }) { + const common = { width: size, height: size, viewBox: '0 0 24 24', fill: 'none', stroke: color, strokeWidth: '1.7', strokeLinecap: 'round', strokeLinejoin: 'round' }; + if (name === 'heart') return ; + if (name === 'challenge') return ; + if (name === 'lead') return ; + if (name === 'adapt') return ; + return ; +} + +function DaeunDeskCard({ data }) { + const current = data.daeun.find((item) => item.current) || data.daeun[0]; + return ( +
+
+ +

대운 흐름

+ 10년 단위 운의 흐름을 살펴보세요. +
+
+
+ {data.daeun.map((item, index) => ( + + + {index < data.daeun.length - 1 && ( +
+ +
+ )} +
+ ))} +
+
+
+ 현재 + 대운 해설 ({current?.age}) +
+
+ 자기 확장과 기반을 다지는 시기입니다.
+ 꾸준한 노력과 인내가 결실을 맺고, 커리어와 재정적 성장이 기대됩니다. +
+
+
+
+ ); +} + +function DaeunNodeDesk({ age, gan, label, current }) { + return ( +
+ {current && ( +
현재
+ )} +
{age}
+
+ {gan} +
+
{label}
+
+ ); +} + +function ConsultCTA({ onClick }) { + return ( +
+
+
+ + 1:1 PERSONAL CONSULT +
+
+ 더 깊은 해석이 필요하신가요? +
+
+ 개인 맞춤 상담을 통해 당신의 사주를 더 깊이 이해하고 명확한 방향을 찾아보세요. +
+
+
); } diff --git a/src/pages/saju/views/today.desktop.jsx b/src/pages/saju/views/today.desktop.jsx index e9caa33..120f361 100644 --- a/src/pages/saju/views/today.desktop.jsx +++ b/src/pages/saju/views/today.desktop.jsx @@ -1,10 +1,245 @@ import React from 'react'; -import TodayMobile from './today.mobile.jsx'; +import { useNavigate } from 'react-router-dom'; +import DesktopFooter from '../_shell/DesktopFooter'; +import Mascot from '../_shell/Mascot'; +import PanelHeader from '../_shell/PanelHeader'; +import { + IconClock, IconHeart, IconMoney, IconPaw, IconSparkle, IconStar, IconSun, +} from '../_shell/Icons'; +import hexA from '../_shell/helpers/hexA'; + +const SCORE_LABELS = [ + { key: 'wealth', label: '재물운', color: '#D4AF37', icon: IconMoney, desc: '안정적인 흐름, 수입에 긍정적인 변화가 있어요.' }, + { key: 'romance', label: '연애운', color: '#D89098', icon: IconHeart, desc: '진심이 통하는 하루, 관계가 한층 가까워져요.' }, + { key: 'social', label: '건강운', color: '#4E6B5C', icon: LeafIcon, desc: '컨디션이 무난해요. 규칙적인 관리가 필요해요.' }, + { key: 'career', label: '직장운', color: '#3A5A8C', icon: BriefcaseIcon, desc: '업무 성과가 좋아요. 기획력이 빛을 발합니다.' }, +]; export default function TodayDesktop({ reading }) { + const navigate = useNavigate(); + const scores = reading?.fortune_scores || {}; + const lucky = reading?.lucky || {}; + const overall = Math.round(scores.overall || 78); + const today = new Date().toLocaleDateString('ko-KR', { + year: 'numeric', month: '2-digit', day: '2-digit', weekday: 'short', + }); + return ( -
- +
+
+
+ 홈  ›  오늘의 운세 +
+ +
+ + +
+
+
+
+

오늘의 운세

+
오늘의 흐름을 한눈에 확인해 보세요.
+
+
+
{today}
+ +
+
+
+ +
+
+
오늘의 종합운
+
+ {overall}/100 +
+
+ {[1, 2, 3, 4, 5].map((i) => )} +
+
+
+
+ 새로운 기회가 찾아오는 날입니다. +
+
+ 작은 실천이 큰 변화를 만듭니다. 주변의 조언에 귀 기울여 보세요.
+ 따뜻한 말 한마디가 당신의 하루를 빛나게 할 것입니다. +
+
+
+ +
+ {SCORE_LABELS.map((item) => ( + + ))} +
+ +
+ +
+ {(Array.isArray(lucky.color) ? lucky.color : ['#1F2A44', '#E8C76B', '#6B4423', '#D89098', '#F7F2E8']).map((color) => ( +
+ ))} +
+ + +
+ + {lucky.time || '오전 10시 ~ 12시'} +
+
+ +
+ 기회는 준비된 마음을
늘 찾아옵니다. +
+
+ +
+ 충동적인 결정은 피하고,
여유를 가지세요. +
+
+
+ +
+
+
더 깊이 알고 싶으신가요?
+
+ 오늘의 운세를 넘어, 당신만을 위한 정밀한 사주 분석으로 인생의 방향을 찾아드려요. +
+
+ + +
+
+
+
+ +
+ ); +} + +function FortuneCard({ label, value, icon: IconComponent, desc, color }) { + return ( +
+
+
+ {React.createElement(IconComponent, { size: 20, stroke: color })} +
+
{label}
+
+
+ {value}/100 +
+
{desc}
); } + +function SmallCard({ color, icon: IconComponent, title, sub, children }) { + return ( +
+ + {React.createElement(IconComponent, { size: 15, stroke: color })} +
+ )} /> +
{sub}
+ {children} +
+ ); +} + +function buttonPrimary() { + return { + padding: '14px 24px', borderRadius: 999, background: '#1F2A44', color: '#F7F2E8', + border: '1px solid rgba(212,175,55,0.4)', fontSize: 14, fontWeight: 800, + boxShadow: '0 4px 14px rgba(31,42,68,0.25), inset 0 1px 0 rgba(212,175,55,0.3)', + display: 'flex', alignItems: 'center', gap: 8, whiteSpace: 'nowrap', + }; +} + +function buttonGhost() { + return { + padding: '14px 24px', borderRadius: 999, background: 'transparent', color: '#1F2A44', + border: '1px solid rgba(31,42,68,0.25)', fontSize: 14, fontWeight: 800, + display: 'flex', alignItems: 'center', gap: 8, whiteSpace: 'nowrap', + }; +} + +function LeafIcon({ size = 16, stroke = '#4E6B5C' }) { + return ( + + + + + + ); +} + +function BriefcaseIcon({ size = 16, stroke = '#3A5A8C' }) { + return ( + + + + + ); +} + +function WarnIcon({ size = 14, stroke = '#C04A4A' }) { + return ( + + + + + ); +} diff --git a/src/pages/saju/views/today.mobile.jsx b/src/pages/saju/views/today.mobile.jsx index f827e7e..05ff27a 100644 --- a/src/pages/saju/views/today.mobile.jsx +++ b/src/pages/saju/views/today.mobile.jsx @@ -70,7 +70,7 @@ export default function TodayMobile({ reading }) { )}
- navigate(`/saju/result?rid=${reading?.id || ''}`)}> + navigate(reading?.id ? `/saju/result?rid=${reading.id}` : '/saju/result')}> 내 사주 자세히 보기