chore(saju-ui-v2): v1 components/ + Saju.css 일괄 삭제 (Phase 6 cleanup)
This commit is contained in:
@@ -1,430 +0,0 @@
|
||||
/* saju-page scope — 다른 페이지에 영향 없음 */
|
||||
.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;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.saju-page * { box-sizing: border-box; }
|
||||
|
||||
.saju-page .saju-h1,
|
||||
.saju-page .saju-h2,
|
||||
.saju-page .saju-h3 {
|
||||
font-family: 'Noto Serif KR', 'Pretendard', serif;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--saju-ink);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.saju-page .saju-h1 { font-size: clamp(2.5rem, 4vw, 3.5rem); line-height: 1.2; }
|
||||
.saju-page .saju-h2 { font-size: clamp(1.8rem, 3vw, 2.5rem); line-height: 1.3; }
|
||||
.saju-page .saju-h3 { font-size: clamp(1.2rem, 2vw, 1.5rem); }
|
||||
|
||||
/* 호령 마스코트 */
|
||||
.horyung-mascot { display: block; object-fit: contain; }
|
||||
.horyung-mascot--sm { width: 80px; height: auto; }
|
||||
.horyung-mascot--md { width: 180px; height: auto; }
|
||||
.horyung-mascot--lg { width: 320px; height: auto; }
|
||||
|
||||
/* 상단 네비게이션 */
|
||||
.saju-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem 2rem;
|
||||
background: var(--saju-ink);
|
||||
color: var(--saju-cream);
|
||||
}
|
||||
.saju-nav__logo {
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--saju-cream);
|
||||
text-decoration: none;
|
||||
}
|
||||
.saju-nav__links {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.saju-nav__links a {
|
||||
color: var(--saju-cream);
|
||||
text-decoration: none;
|
||||
font-size: 0.95rem;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.saju-nav__links a:hover { opacity: 1; }
|
||||
.saju-nav__cta {
|
||||
background: var(--saju-gold);
|
||||
color: var(--saju-ink);
|
||||
border: none;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: 'Pretendard', sans-serif;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* Hero */
|
||||
.saju-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.4fr;
|
||||
gap: 3rem;
|
||||
padding: 3rem 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.saju-hero__left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
.saju-quote-box {
|
||||
background: var(--saju-paper);
|
||||
padding: 1rem 1.25rem;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--saju-gold-deep);
|
||||
color: var(--saju-ink);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
max-width: 280px;
|
||||
}
|
||||
.saju-hero__right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
.saju-sub {
|
||||
color: var(--saju-ink);
|
||||
opacity: 0.7;
|
||||
margin: 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ActionCard */
|
||||
.saju-action-cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.saju-action-card {
|
||||
background: var(--saju-saju-bg);
|
||||
color: var(--saju-cream);
|
||||
padding: 1.5rem 1rem;
|
||||
border-radius: 16px;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: transform 0.2s;
|
||||
font-family: 'Pretendard', sans-serif;
|
||||
}
|
||||
.saju-action-card:hover { transform: translateY(-4px); }
|
||||
.saju-action-card--today { background: var(--saju-today-bg); }
|
||||
.saju-action-card--gunghab { background: var(--saju-gunghab-bg); }
|
||||
.saju-action-card--saju { background: var(--saju-saju-bg); }
|
||||
.saju-action-card[aria-disabled="true"] { opacity: 0.6; cursor: not-allowed; }
|
||||
.saju-action-card__icon { font-size: 2rem; }
|
||||
.saju-action-card__title { font-size: 1.1rem; font-weight: 700; }
|
||||
.saju-action-card__desc { font-size: 0.85rem; opacity: 0.85; text-align: center; }
|
||||
|
||||
/* Bottom */
|
||||
.saju-bottom {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 3rem;
|
||||
padding: 3rem 2rem;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: var(--saju-ink);
|
||||
color: var(--saju-cream);
|
||||
border-radius: 24px 24px 0 0;
|
||||
}
|
||||
.saju-form { display: flex; flex-direction: column; gap: 1rem; }
|
||||
.saju-form input,
|
||||
.saju-form select {
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--saju-gold-deep);
|
||||
background: var(--saju-ink-deep);
|
||||
color: var(--saju-cream);
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.saju-form button {
|
||||
background: var(--saju-gold);
|
||||
color: var(--saju-ink);
|
||||
border: none;
|
||||
padding: 0.875rem;
|
||||
border-radius: 999px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.saju-form button:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
.saju-form__error {
|
||||
background: rgba(217, 162, 166, 0.2);
|
||||
color: var(--saju-rose);
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Fortune ring */
|
||||
.saju-fortune-ring {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
.saju-fortune-ring svg { width: 200px; height: 200px; }
|
||||
.saju-fortune-ring__score {
|
||||
position: absolute;
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--saju-ink);
|
||||
}
|
||||
.saju-fortune-ring__total { font-size: 0.9rem; color: var(--saju-ink); opacity: 0.6; }
|
||||
|
||||
/* ScoreCard */
|
||||
.saju-score-card {
|
||||
background: var(--saju-cream);
|
||||
border-radius: 16px;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
border: 1px solid var(--saju-paper);
|
||||
}
|
||||
.saju-score-card__head { display: flex; align-items: center; gap: 0.5rem; }
|
||||
.saju-score-card__icon { font-size: 1.5rem; }
|
||||
.saju-score-card__title { font-weight: 700; font-size: 0.95rem; }
|
||||
.saju-score-card__value {
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--saju-ink);
|
||||
}
|
||||
.saju-score-card__bar {
|
||||
height: 6px;
|
||||
background: var(--saju-paper);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.saju-score-card__bar > div { height: 100%; background: var(--saju-gold); transition: width 0.5s; }
|
||||
|
||||
/* Lucky box */
|
||||
.saju-lucky-box {
|
||||
background: var(--saju-paper);
|
||||
border-radius: 16px;
|
||||
padding: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
.saju-lucky-box__item { text-align: center; }
|
||||
.saju-lucky-box__label { font-size: 0.8rem; color: var(--saju-ink); opacity: 0.7; margin-bottom: 0.25rem; }
|
||||
.saju-lucky-box__value {
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: var(--saju-ink);
|
||||
}
|
||||
|
||||
/* SajuPillars */
|
||||
.saju-pillars {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.saju-pillar {
|
||||
background: var(--saju-paper);
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.saju-pillar__label { font-size: 0.8rem; color: var(--saju-ink); opacity: 0.6; margin-bottom: 0.5rem; }
|
||||
.saju-pillar__stem,
|
||||
.saju-pillar__branch {
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 700;
|
||||
display: block;
|
||||
}
|
||||
.saju-pillar__stem-kr,
|
||||
.saju-pillar__branch-kr { font-size: 0.85rem; opacity: 0.7; }
|
||||
.saju-pillar__ten-god,
|
||||
.saju-pillar__fortune { font-size: 0.75rem; margin-top: 0.25rem; opacity: 0.7; }
|
||||
|
||||
/* Element bars */
|
||||
.saju-element-bars {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 1.5rem;
|
||||
background: var(--saju-cream);
|
||||
border-radius: 16px;
|
||||
}
|
||||
.saju-element-bar {
|
||||
display: grid;
|
||||
grid-template-columns: 60px 1fr 50px;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.saju-element-bar__label { font-size: 0.9rem; font-weight: 700; }
|
||||
.saju-element-bar__track {
|
||||
height: 12px;
|
||||
background: var(--saju-paper);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.saju-element-bar__fill {
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
transition: width 0.5s;
|
||||
}
|
||||
.saju-element-bar__fill--木 { background: #4B7065; }
|
||||
.saju-element-bar__fill--火 { background: #C56F5C; }
|
||||
.saju-element-bar__fill--土 { background: #D4A574; }
|
||||
.saju-element-bar__fill--金 { background: #B8B5A8; }
|
||||
.saju-element-bar__fill--水 { background: #4A5878; }
|
||||
.saju-element-bar__value { text-align: right; font-size: 0.85rem; opacity: 0.7; }
|
||||
|
||||
/* Monthly flow */
|
||||
.saju-monthly-flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, 1fr);
|
||||
gap: 0.25rem;
|
||||
padding: 1rem;
|
||||
background: var(--saju-cream);
|
||||
border-radius: 16px;
|
||||
}
|
||||
.saju-monthly-flow__cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0.25rem;
|
||||
border-radius: 8px;
|
||||
background: var(--saju-paper);
|
||||
}
|
||||
.saju-monthly-flow__month { font-size: 0.7rem; opacity: 0.7; }
|
||||
.saju-monthly-flow__score {
|
||||
font-family: 'Noto Serif KR', serif;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.saju-monthly-flow__label { font-size: 0.7rem; opacity: 0.8; margin-top: 0.25rem; }
|
||||
|
||||
/* Horyung quote */
|
||||
.saju-horyung-quote {
|
||||
background: var(--saju-ink);
|
||||
color: var(--saju-cream);
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.saju-horyung-quote__text { font-size: 0.95rem; line-height: 1.6; }
|
||||
|
||||
/* Interpret accordion */
|
||||
.saju-interpret-accordion { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||
.saju-interpret-item {
|
||||
background: var(--saju-cream);
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--saju-paper);
|
||||
overflow: hidden;
|
||||
}
|
||||
.saju-interpret-item__header {
|
||||
padding: 1rem;
|
||||
background: var(--saju-paper);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
user-select: none;
|
||||
}
|
||||
.saju-interpret-item__body { padding: 1rem; font-size: 0.95rem; line-height: 1.6; }
|
||||
.saju-interpret-item__evidence {
|
||||
background: var(--saju-paper);
|
||||
padding: 0.75rem;
|
||||
border-radius: 8px;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Stub */
|
||||
.saju-stub {
|
||||
max-width: 480px;
|
||||
margin: 5rem auto;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background: var(--saju-paper);
|
||||
border-radius: 24px;
|
||||
}
|
||||
.saju-stub a {
|
||||
display: inline-block;
|
||||
margin-top: 1.5rem;
|
||||
background: var(--saju-gold);
|
||||
color: var(--saju-ink);
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 999px;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 반응형 */
|
||||
@media (max-width: 1280px) {
|
||||
.saju-hero { grid-template-columns: 1fr; text-align: center; }
|
||||
.saju-hero__left { order: 2; }
|
||||
.saju-hero__right { order: 1; }
|
||||
.saju-bottom { grid-template-columns: 1fr; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.saju-nav { padding: 0.75rem 1rem; flex-wrap: wrap; gap: 0.5rem; }
|
||||
.saju-nav__links { display: none; }
|
||||
.saju-action-cards { grid-template-columns: 1fr; }
|
||||
.saju-pillars { grid-template-columns: repeat(2, 1fr); }
|
||||
.saju-monthly-flow { grid-template-columns: repeat(4, 1fr); }
|
||||
.horyung-mascot--lg { width: 220px; }
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
const ICON = {
|
||||
today: '☀',
|
||||
heart: '♥',
|
||||
book: '📖',
|
||||
};
|
||||
|
||||
export default function ActionCard({ to, icon, title, desc, variant = 'saju', disabled = false }) {
|
||||
const cls = `saju-action-card saju-action-card--${variant}`;
|
||||
if (disabled) {
|
||||
return (
|
||||
<span className={cls} aria-disabled="true">
|
||||
<span className="saju-action-card__icon">{ICON[icon] || '✦'}</span>
|
||||
<span className="saju-action-card__title">{title}</span>
|
||||
<span className="saju-action-card__desc">{desc || '준비 중'}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={to} className={cls}>
|
||||
<span className="saju-action-card__icon">{ICON[icon] || '✦'}</span>
|
||||
<span className="saju-action-card__title">{title}</span>
|
||||
<span className="saju-action-card__desc">{desc}</span>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const ELEMENT_ORDER = ['木', '火', '土', '金', '水'];
|
||||
const ELEMENT_KR = { '木': '목', '火': '화', '土': '토', '金': '금', '水': '수' };
|
||||
|
||||
export default function ElementBarChart({ scores }) {
|
||||
if (!scores) return null;
|
||||
const max = Math.max(...Object.values(scores), 1);
|
||||
return (
|
||||
<div className="saju-element-bars">
|
||||
{ELEMENT_ORDER.map((e) => {
|
||||
const value = scores[e] || 0;
|
||||
const widthPct = (value / max) * 100;
|
||||
return (
|
||||
<div key={e} className="saju-element-bar">
|
||||
<div className="saju-element-bar__label">{e} ({ELEMENT_KR[e]})</div>
|
||||
<div className="saju-element-bar__track">
|
||||
<div
|
||||
className={`saju-element-bar__fill saju-element-bar__fill--${e}`}
|
||||
style={{ width: `${widthPct}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="saju-element-bar__value">{value.toFixed(1)}%</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function FortuneRing({ score, max = 100 }) {
|
||||
const radius = 80;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
const safe = Math.max(0, Math.min(score || 0, max));
|
||||
const dashOffset = circumference - (safe / max) * circumference;
|
||||
|
||||
return (
|
||||
<div className="saju-fortune-ring">
|
||||
<svg viewBox="0 0 200 200">
|
||||
<circle
|
||||
cx="100" cy="100" r={radius}
|
||||
stroke="var(--saju-paper)" strokeWidth="14" fill="none"
|
||||
/>
|
||||
<circle
|
||||
cx="100" cy="100" r={radius}
|
||||
stroke="var(--saju-gold)" strokeWidth="14" fill="none"
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={dashOffset}
|
||||
strokeLinecap="round"
|
||||
transform="rotate(-90 100 100)"
|
||||
style={{ transition: 'stroke-dashoffset 0.6s ease' }}
|
||||
/>
|
||||
</svg>
|
||||
<div style={{ position: 'absolute', textAlign: 'center' }}>
|
||||
<div className="saju-fortune-ring__score">{safe}</div>
|
||||
<div className="saju-fortune-ring__total">/ {max}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const POSE_TO_FILE = {
|
||||
front: '/images/saju/horyung/horyung-front.png',
|
||||
bust: '/images/saju/horyung/horyung-bust.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',
|
||||
};
|
||||
|
||||
export default function HoryungMascot({ pose = 'front', size = 'lg', className = '' }) {
|
||||
const src = POSE_TO_FILE[pose] || POSE_TO_FILE.front;
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
alt="호령"
|
||||
className={`horyung-mascot horyung-mascot--${size} ${className}`}
|
||||
onError={(e) => { e.target.style.visibility = 'hidden'; }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import React from 'react';
|
||||
import HoryungMascot from './HoryungMascot';
|
||||
|
||||
export default function HoryungQuote({ pose = 'thinking', text }) {
|
||||
if (!text) return null;
|
||||
return (
|
||||
<div className="saju-horyung-quote">
|
||||
<HoryungMascot pose={pose} size="sm" />
|
||||
<div className="saju-horyung-quote__text">{text}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default function InterpretAccordion({ items }) {
|
||||
const [openKey, setOpenKey] = useState(items?.[0]?.key);
|
||||
if (!items || items.length === 0) return null;
|
||||
return (
|
||||
<div className="saju-interpret-accordion">
|
||||
{items.map((it) => {
|
||||
const isOpen = openKey === it.key;
|
||||
return (
|
||||
<div key={it.key} className="saju-interpret-item">
|
||||
<div
|
||||
className="saju-interpret-item__header"
|
||||
onClick={() => setOpenKey(isOpen ? null : it.key)}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') setOpenKey(isOpen ? null : it.key); }}
|
||||
>
|
||||
<span>{it.title || it.key}</span>
|
||||
<span aria-hidden>{isOpen ? '▾' : '▸'}</span>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="saju-interpret-item__body">
|
||||
<p style={{ margin: 0 }}>{it.content}</p>
|
||||
{it.evidence && (
|
||||
<div className="saju-interpret-item__evidence">
|
||||
<strong>근거:</strong> {it.evidence.saju_element}<br />
|
||||
<strong>해석 논리:</strong> {it.evidence.reasoning}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function LuckyBox({ lucky }) {
|
||||
if (!lucky) return null;
|
||||
return (
|
||||
<div className="saju-lucky-box">
|
||||
<div className="saju-lucky-box__item">
|
||||
<div className="saju-lucky-box__label">럭키 컬러</div>
|
||||
<div className="saju-lucky-box__value">{(lucky.color || []).join(' · ')}</div>
|
||||
</div>
|
||||
<div className="saju-lucky-box__item">
|
||||
<div className="saju-lucky-box__label">럭키 숫자</div>
|
||||
<div className="saju-lucky-box__value">{lucky.number}</div>
|
||||
</div>
|
||||
<div className="saju-lucky-box__item">
|
||||
<div className="saju-lucky-box__label">럭키 방향</div>
|
||||
<div className="saju-lucky-box__value">{lucky.direction}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const LABEL_COLOR = {
|
||||
'성장': '#4B7065',
|
||||
'안정': '#D4A574',
|
||||
'변동': '#6A5285',
|
||||
'도전': '#C58F76',
|
||||
'정체': '#888',
|
||||
};
|
||||
|
||||
export default function MonthlyFlow({ flow }) {
|
||||
if (!flow || flow.length === 0) return null;
|
||||
return (
|
||||
<div className="saju-monthly-flow">
|
||||
{flow.map((m) => (
|
||||
<div key={m.month} className="saju-monthly-flow__cell">
|
||||
<span className="saju-monthly-flow__month">{m.month}월</span>
|
||||
<span className="saju-monthly-flow__score" style={{ color: LABEL_COLOR[m.label] }}>
|
||||
{m.score}
|
||||
</span>
|
||||
<span className="saju-monthly-flow__label">{m.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function SajuInputForm({ form, onChange, onSubmit, loading, error }) {
|
||||
return (
|
||||
<form className="saju-form" onSubmit={onSubmit}>
|
||||
<h3 className="saju-h3" style={{ color: 'var(--saju-cream)', marginBottom: '0.5rem' }}>
|
||||
사주풀이 시작하기
|
||||
</h3>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="이름 (선택)"
|
||||
value={form.name}
|
||||
onChange={(e) => onChange('name', e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.5rem' }}>
|
||||
<input type="number" placeholder="년 (1900-2100)" value={form.year}
|
||||
onChange={(e) => onChange('year', e.target.value)} disabled={loading} min="1900" max="2100" />
|
||||
<input type="number" placeholder="월" value={form.month}
|
||||
onChange={(e) => onChange('month', e.target.value)} disabled={loading} min="1" max="12" />
|
||||
<input type="number" placeholder="일" value={form.day}
|
||||
onChange={(e) => onChange('day', e.target.value)} disabled={loading} min="1" max="31" />
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '0.5rem' }}>
|
||||
<input type="number" placeholder="시 (선택, 0-23)" value={form.hour}
|
||||
onChange={(e) => onChange('hour', e.target.value)} disabled={loading} min="0" max="23" />
|
||||
<select value={form.gender} onChange={(e) => onChange('gender', e.target.value)} disabled={loading}>
|
||||
<option value="male">남</option>
|
||||
<option value="female">여</option>
|
||||
</select>
|
||||
<select value={form.calendar_type} onChange={(e) => onChange('calendar_type', e.target.value)} disabled={loading}>
|
||||
<option value="solar">양력</option>
|
||||
<option value="lunar">음력</option>
|
||||
</select>
|
||||
</div>
|
||||
{error && <div className="saju-form__error">{error}</div>}
|
||||
<button type="submit" disabled={loading}>
|
||||
{loading ? '호령이 풀어보는 중...' : '사주풀이 시작하기 ✦'}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
|
||||
export default function SajuNav() {
|
||||
return (
|
||||
<nav className="saju-nav" aria-label="호령 사주">
|
||||
<Link to="/saju" className="saju-nav__logo">호령사주</Link>
|
||||
<ul className="saju-nav__links">
|
||||
<li><NavLink to="/saju/today">오늘의 운세</NavLink></li>
|
||||
<li><NavLink to="/saju/compatibility">궁합보기</NavLink></li>
|
||||
<li><NavLink to="/saju/result">사주풀이</NavLink></li>
|
||||
</ul>
|
||||
<Link to="/saju" className="saju-nav__cta">사주풀이 시작하기</Link>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const PILLAR_LABELS = { year: '년주', month: '월주', day: '일주', hour: '시주' };
|
||||
|
||||
export default function SajuPillars({ saju }) {
|
||||
if (!saju) return null;
|
||||
const pillars = ['year', 'month', 'day', 'hour'];
|
||||
return (
|
||||
<div className="saju-pillars">
|
||||
{pillars.map((p) => {
|
||||
const data = saju[p];
|
||||
if (!data) {
|
||||
return (
|
||||
<div key={p} className="saju-pillar">
|
||||
<div className="saju-pillar__label">{PILLAR_LABELS[p]}</div>
|
||||
<div style={{ opacity: 0.4 }}>-</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div key={p} className="saju-pillar">
|
||||
<div className="saju-pillar__label">{PILLAR_LABELS[p]}</div>
|
||||
<div>
|
||||
<span className="saju-pillar__stem">{data.stem}</span>
|
||||
<span className="saju-pillar__stem-kr"> ({data.stem_kr})</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="saju-pillar__branch">{data.branch}</span>
|
||||
<span className="saju-pillar__branch-kr"> ({data.branch_kr})</span>
|
||||
</div>
|
||||
<div className="saju-pillar__ten-god">{data.ten_god}</div>
|
||||
<div className="saju-pillar__fortune">{data.fortune}</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const ICON_BY_CATEGORY = {
|
||||
wealth: '💰',
|
||||
romance: '💖',
|
||||
social: '🤝',
|
||||
career: '💼',
|
||||
};
|
||||
|
||||
const COLOR_VAR_BY_CATEGORY = {
|
||||
wealth: 'var(--saju-wealth)',
|
||||
romance: 'var(--saju-romance)',
|
||||
social: 'var(--saju-social)',
|
||||
career: 'var(--saju-career)',
|
||||
};
|
||||
|
||||
const TITLE_BY_CATEGORY = {
|
||||
wealth: '재물운',
|
||||
romance: '연애운',
|
||||
social: '인간관계',
|
||||
career: '직장운',
|
||||
};
|
||||
|
||||
export default function ScoreCard({ category, score }) {
|
||||
const safe = Math.max(0, Math.min(score || 0, 100));
|
||||
return (
|
||||
<div className="saju-score-card">
|
||||
<div className="saju-score-card__head">
|
||||
<span className="saju-score-card__icon">{ICON_BY_CATEGORY[category]}</span>
|
||||
<span className="saju-score-card__title">{TITLE_BY_CATEGORY[category]}</span>
|
||||
</div>
|
||||
<div className="saju-score-card__value">{safe}<small style={{ fontSize: '1rem', opacity: 0.5 }}>/100</small></div>
|
||||
<div className="saju-score-card__bar">
|
||||
<div style={{ width: `${safe}%`, background: COLOR_VAR_BY_CATEGORY[category] }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user