Enhance tarot reading experience

This commit is contained in:
2026-05-24 12:39:20 +09:00
parent 6d73a075f7
commit 94569a4c45
5 changed files with 2031 additions and 111 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -4,14 +4,27 @@ import { TAROT_DECK, CATEGORIES, SPREADS } from './data/cards';
import { useTarotShuffle } from './hooks/useTarotShuffle'; import { useTarotShuffle } from './hooks/useTarotShuffle';
import { useTarotReading } from './hooks/useTarotReading'; import { useTarotReading } from './hooks/useTarotReading';
import TarotCard from './components/TarotCard'; import TarotCard from './components/TarotCard';
import CardGrid from './components/CardGrid';
import SpreadSlots from './components/SpreadSlots';
import InterpretationPanel from './components/InterpretationPanel'; import InterpretationPanel from './components/InterpretationPanel';
const STEPS = [ const STEPS = [
{ id: 1, label: '질문 & 설정' }, { id: 1, label: '질문 & 설정' },
{ id: 2, label: '카드 선택' }, { id: 2, label: '카드 선택' },
{ id: 3, label: '해석' }, { id: 3, label: '해석 보기' },
];
const CATEGORY_LABELS = {
'연애': '♡ 연애',
'일·커리어': '직업',
'관계': '인간관계',
'재물': '재물',
'건강': '건강',
'일반': '기타',
};
const RECENT_READINGS = [
{ title: '연애운에 대해', meta: '3장 스프레드 · 방금 전' },
{ title: '새로운 직장에 대한 고민', meta: '3장 스프레드 · 어제' },
{ title: '오늘의 운세', meta: '3장 스프레드 · 2일 전' },
]; ];
function StepIndicator({ current }) { function StepIndicator({ current }) {
@@ -39,23 +52,32 @@ export default function Reading() {
const [focusIdx, setFocusIdx] = useState(null); const [focusIdx, setFocusIdx] = useState(null);
const spread = SPREADS[spreadId]; const spread = SPREADS[spreadId];
const { slice, reshuffle } = useTarotShuffle(TAROT_DECK, 16); const { slice, reshuffle } = useTarotShuffle(TAROT_DECK, 20);
const { status, interpretation, runInterpretAndSave, error } = useTarotReading(); const { status, interpretation, runInterpretAndSave, error } = useTarotReading();
const startShuffle = () => { const startShuffle = () => {
reshuffle(); reshuffle();
setPicks([]);
setFocusIdx(null);
setStep(1);
};
const openCardSpread = () => {
setPicks([]); setPicks([]);
setFocusIdx(null); setFocusIdx(null);
setStep(2); setStep(2);
}; };
const handlePick = (card) => { const handlePick = (card = null) => {
if (picks.length >= spread.positions.length) return; if (picks.length >= spread.positions.length) return;
const idx = picks.length; const idx = picks.length;
const nextCard = card || slice.find((c) => !disabledIds.includes(c.slug));
if (!nextCard) return;
const pos = spread.positions[idx]; const pos = spread.positions[idx];
const next = [...picks, { card, position: pos.label, reversed: card.reversed }]; const next = [...picks, { card: nextCard, position: pos.label, reversed: nextCard.reversed }];
setPicks(next); setPicks(next);
if (next.length === spread.positions.length) setFocusIdx(0); setFocusIdx(idx);
setStep(next.length === spread.positions.length ? 3 : 2);
}; };
const handleInterpret = async () => { const handleInterpret = async () => {
@@ -72,128 +94,285 @@ export default function Reading() {
setStep(1); setPicks([]); setFocusIdx(null); setStep(1); setPicks([]); setFocusIdx(null);
}; };
const resetCards = () => {
reshuffle();
setPicks([]);
setFocusIdx(null);
setStep(1);
};
const changeSpread = (nextSpreadId) => {
setSpreadId(nextSpreadId);
setPicks([]);
setFocusIdx(null);
setStep(1);
reshuffle();
};
const disabledIds = picks.map((p) => p.card.slug); const disabledIds = picks.map((p) => p.card.slug);
const focusCard = focusIdx !== null && picks[focusIdx] ? picks[focusIdx].card : null; const focusPick = focusIdx !== null && picks[focusIdx] ? picks[focusIdx] : picks[picks.length - 1] || null;
const focusCard = focusPick?.card || null;
const focusCardId = focusCard?.slug; const focusCardId = focusCard?.slug;
const allPicked = picks.length === spread.positions.length; const allPicked = picks.length === spread.positions.length;
const busy = status === 'interpreting' || status === 'saving'; const busy = status === 'interpreting' || status === 'saving';
const remaining = slice.filter((c) => !disabledIds.includes(c.slug)); const currentPosition = step > 1 ? spread.positions[picks.length] : null;
const canDraw = picks.length < spread.positions.length;
const selectionOpen = step === 2 && !allPicked;
const remainingCount = spread.positions.length - picks.length;
return ( return (
<div className="tarot tarot-reading-page"> <div className="tarot tarot-reading-page">
<StepIndicator current={step} />
<div className="tarot-reading"> <div className="tarot-reading">
<aside className="tarot-reading__col"> <aside className="tarot-reading__col tarot-reading__settings">
<div className="tarot-reading__step-label">질문</div> <div className="tarot-reading__field-head">
<span>1. 질문을 입력하세요</span>
<b>?</b>
</div>
<textarea <textarea
className="tarot-reading__textarea" className="tarot-reading__textarea"
value={question} value={question}
onChange={(e) => setQuestion(e.target.value)} onChange={(e) => setQuestion(e.target.value)}
placeholder="질문을 입력하세요" maxLength={200}
placeholder="궁금한 점을 자유롭게 입력하세요."
/> />
<div className="tarot-reading__char-count">{question.length}/200</div>
<div className="tarot-reading__step-label" style={{ marginTop: 16 }}>카테고리</div> <div className="tarot-reading__field-head">
<span>2. 카테고리 선택</span>
</div>
<div className="tarot-reading__chips"> <div className="tarot-reading__chips">
{CATEGORIES.map((c) => ( {CATEGORIES.map((c) => (
<button <button
key={c} key={c}
className={`tarot-chip ${category === c ? 'is-active' : ''}`} className={`tarot-chip ${category === c ? 'is-active' : ''}`}
onClick={() => setCategory(c)} onClick={() => setCategory(c)}
>{c}</button> >
{CATEGORY_LABELS[c] || c}
</button>
))} ))}
</div> </div>
<div className="tarot-reading__step-label" style={{ marginTop: 16 }}>스프레드 선택</div> <div className="tarot-reading__field-head">
<div className="tarot-reading__radio-row"> <span>3. 스프레드 선택</span>
<label> <b>i</b>
<input type="radio" checked={spreadId === 'three_card'} </div>
onChange={() => setSpreadId('three_card')} /> 3 (과거 · 현재 · 미래) <div className="tarot-spread-picker">
</label> <button
<label> type="button"
<input type="radio" checked={spreadId === 'one_card'} className={`tarot-spread-option ${spreadId === 'three_card' ? 'is-active' : ''}`}
onChange={() => setSpreadId('one_card')} /> 1 (오늘의 카드) onClick={() => changeSpread('three_card')}
</label> >
<span className="tarot-spread-option__icon" aria-hidden>
<i /><i /><i />
</span>
<span>
<strong>3 스프레드</strong>
<em>과거 · 현재 · 미래</em>
</span>
<b aria-hidden></b>
</button>
<button
type="button"
className={`tarot-spread-option ${spreadId === 'one_card' ? 'is-active' : ''}`}
onClick={() => changeSpread('one_card')}
>
<span className="tarot-spread-option__icon tarot-spread-option__icon--single" aria-hidden>
<i />
</span>
<span>
<strong>오늘의 카드</strong>
<em>하루의 흐름</em>
</span>
<b aria-hidden></b>
</button>
</div> </div>
{step === 1 && ( <div className="tarot-deck-box">
<button className="tarot-reading__primary" onClick={startShuffle}> <img src="/images/tarot/card_back.png" alt="" aria-hidden />
카드 셔플하기 <div>
</button> <strong>아르카나 </strong>
)} <span>메이저 22 · 마이너 56</span>
<button type="button" onClick={startShuffle}> 변경</button>
</div>
</div>
{step === 2 && !allPicked && ( <button className="tarot-reading__secondary-action" onClick={resetCards}>
<button className="tarot-reading__primary" onClick={() => reshuffle()}> 카드 섞기
다시 셔플
</button> </button>
)}
{step === 2 && allPicked && ( <button
<button className="tarot-reading__primary" onClick={handleInterpret} disabled={busy}> className="tarot-reading__primary"
{busy ? '해석 중…' : 'AI 해석 시작'} onClick={() => (allPicked ? handleInterpret() : openCardSpread())}
disabled={busy || (!allPicked && step === 2)}
>
{allPicked ? (busy ? '해석 중...' : '카드 해석하기') : step === 2 ? '카드를 선택하세요' : '카드 뽑기'}
<span>
{allPicked
? 'AI 리딩을 시작합니다'
: step === 2
? `${remainingCount}장을 더 선택하면 리딩을 시작할 수 있습니다`
: '20장의 카드 중 직감이 닿는 카드를 선택합니다'}
</span>
</button> </button>
)}
{step === 3 && ( {picks.length > 0 && (
<button className="tarot-reading__primary" onClick={restart}> <button className="tarot-reading__ghost-action" onClick={restart}>
리딩 리딩
</button> </button>
)} )}
{error && <p style={{ color: '#f43f5e', marginTop: 12, fontSize: 13 }}>{error}</p>} {error && <p className="tarot-reading__error">{error}</p>}
<p className="tarot-reading__hint"> 직관을 믿고 마음을 가라앉힌 카드를 뽑아보세요.</p>
</aside> </aside>
<div className="tarot-reading__col tarot-reading__center"> <div className="tarot-reading__col tarot-reading__center">
{step < 2 && ( <StepIndicator current={step} />
<p style={{ color: 'var(--tarot-text-dim)', textAlign: 'center', padding: '60px 20px' }}>
좌측에서 질문·카테고리·스프레드를 선택하고 셔플하세요.
</p>
)}
{step === 2 && ( <div className="tarot-reading__prompt">
<> <strong>{allPicked ? '선택한 카드가 스프레드에 놓였습니다.' : selectionOpen ? '펼쳐진 카드 중 마음이 끌리는 카드를 선택하세요.' : '마음의 평화를 유지하고 직관을 통해 메시지를 받아보세요.'}</strong>
{!allPicked ? ( <span>{allPicked ? '카드를 선택하면 오른쪽에서 의미를 확인할 수 있습니다.' : currentPosition ? `${currentPosition.label} 위치에 놓을 카드를 선택할 차례입니다.` : '좌측에서 질문과 스프레드를 설정한 뒤 카드를 뽑아보세요.'}</span>
<> </div>
<p style={{ color: 'var(--tarot-text-dim)', marginBottom: 20, letterSpacing: 1 }}>
카드 {picks.length + 1}/{spread.positions.length} <span style={{ color: 'var(--tarot-gold)' }}>{spread.positions[picks.length].label}</span> <div className="tarot-table">
</p> <div className="tarot-table__rings" aria-hidden />
<CardGrid slice={slice} onPick={handlePick} disabledIds={disabledIds} /> {step === 1 && picks.length === 0 ? (
{picks.length > 0 && ( <div className="tarot-deck-stage">
<SpreadSlots <div className="tarot-deck-pile" aria-hidden>
spread={spread} picks={picks} <img src="/images/tarot/card_bunch.png" alt="" draggable={false} />
onCardClick={(idx) => setFocusIdx(idx)} </div>
<p>{spread.name} 준비 완료</p>
</div>
) : selectionOpen ? (
<div className="tarot-selection-stage">
<div className="tarot-selection-stage__status">
<span>{picks.length}/{spread.positions.length}</span>
<strong>{currentPosition?.label} 카드 선택</strong>
</div>
<div className="tarot-card-spread" aria-label="펼쳐진 타로 카드">
{slice.map((card, idx) => {
const selectedIdx = disabledIds.indexOf(card.slug);
const selectedPick = selectedIdx >= 0 ? picks[selectedIdx] : null;
return (
<div
key={card.slug}
className={`tarot-card-choice ${selectedPick ? 'is-selected' : ''}`}
style={{ '--choice-i': idx }}
>
<TarotCard
card={card}
faceDown
size="sm"
clickable={!selectedPick}
onClick={() => handlePick(card)}
label={`${idx + 1}번째 펼쳐진 카드`}
/> />
{selectedPick && (
<span className="tarot-card-choice__badge">{selectedPick.position}</span>
)} )}
</> </div>
);
})}
</div>
<div className={`tarot-selected-strip tarot-selected-strip--${spread.positions.length}`}>
{spread.positions.map((pos) => {
const pick = picks[pos.idx];
return (
<button
key={pos.idx}
type="button"
className={`tarot-selected-strip__item ${pick ? 'is-filled' : ''}`}
onClick={() => pick && setFocusIdx(pos.idx)}
disabled={!pick}
>
<strong>{pos.label}</strong>
<span>{pick ? pick.card.name : '선택 대기'}</span>
</button>
);
})}
</div>
</div>
) : ( ) : (
<SpreadSlots <div className={`tarot-table__cards tarot-table__cards--${spread.positions.length}`}>
spread={spread} picks={picks} {spread.positions.map((pos) => {
onCardClick={(idx) => setFocusIdx(idx)} const pick = picks[pos.idx];
const isCurrent = pos.idx === picks.length && canDraw;
return (
<div
key={pos.idx}
className={`tarot-table-slot ${pick ? 'is-picked' : ''} ${isCurrent ? 'is-current' : ''}`}
>
<span className="tarot-table-slot__halo" aria-hidden />
{pick ? (
<TarotCard
card={pick.card}
reversed={pick.reversed}
size="lg"
clickable
onClick={() => setFocusIdx(pos.idx)}
/>
) : (
<TarotCard
card={slice[pos.idx]}
faceDown
size="lg"
clickable={isCurrent}
onClick={() => isCurrent && handlePick()}
/> />
)} )}
</> <span className="tarot-table-slot__mark"></span>
)} <span className="tarot-table-slot__label">
<strong>{pos.label}</strong>
{step === 3 && ( <em>{pos.label === '과거' ? 'Past' : pos.label === '현재' ? 'Present' : pos.label === '미래' ? 'Future' : 'Today'}</em>
<SpreadSlots </span>
spread={spread} picks={picks} </div>
onCardClick={(idx) => setFocusIdx(idx)} );
/> })}
)}
{(step === 2 || step === 3) && remaining.length > 0 && allPicked && (
<div className="tarot-reading__preview-row">
{remaining.slice(0, 12).map((c) => (
<TarotCard key={c.slug} card={c} faceDown size="sm" />
))}
</div> </div>
)} )}
</div> </div>
{picks.length > 0 && (
<div className="tarot-reading__center-actions">
<button type="button" onClick={resetCards}> 카드 다시 뽑기</button>
<label>
카드 뒤집기 애니메이션
<span aria-hidden />
</label>
</div>
)}
<div className="tarot-recent">
<div className="tarot-recent__head">
<strong>최근 리딩</strong>
<button type="button">전체 보기 </button>
</div>
<div className="tarot-recent__list">
{RECENT_READINGS.map((item) => (
<article key={item.title} className="tarot-recent-card">
<div className="tarot-recent-card__thumb">
<img src="/images/tarot/card_back.png" alt="" aria-hidden />
</div>
<div>
<strong>{item.title}</strong>
<span>{item.meta}</span>
</div>
</article>
))}
<button type="button" className="tarot-recent-card tarot-recent-card--new">
<span></span>
리딩하기
</button>
</div>
</div>
</div>
<InterpretationPanel <InterpretationPanel
interpretation={interpretation} interpretation={interpretation}
selectedCard={focusCard} selectedCard={focusCard}
focusCardId={focusCardId} focusCardId={focusCardId}
selectedPosition={focusPick?.position}
selectedReversed={!!focusPick?.reversed}
/> />
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -7,35 +7,99 @@ function ConfidenceBadge({ level }) {
return <span className={`tarot-confidence ${cls}`}>확신 {text}</span>; return <span className={`tarot-confidence ${cls}`}>확신 {text}</span>;
} }
export default function InterpretationPanel({ interpretation, selectedCard, focusCardId }) { export default function InterpretationPanel({
interpretation,
selectedCard,
focusCardId,
selectedPosition,
selectedReversed = false,
}) {
const [showEvidence, setShowEvidence] = useState(true); const [showEvidence, setShowEvidence] = useState(true);
if (!interpretation) { if (!interpretation && !selectedCard) {
return ( return (
<aside className="tarot-panel tarot-panel--empty"> <aside className="tarot-panel tarot-panel--reading">
<p>카드를 모두 뽑은 AI 해석을 시작하세요.</p> <div className="tarot-panel__tabs">
<button type="button" className="is-active">카드 해석</button>
<button type="button">AI 인사이트</button>
</div>
<div className="tarot-panel__empty-state">
<span></span>
<p>카드를 뽑으면 이곳에 카드 의미와 AI 인사이트가 표시됩니다.</p>
</div>
</aside> </aside>
); );
} }
const cardDetail = focusCardId const cardDetail = focusCardId
? (interpretation.cards || []).find((c) => c.card === focusCardId) ? (interpretation?.cards || []).find((c) => c.card === focusCardId)
: null; : null;
const keywords = selectedReversed
? selectedCard?.reversedKeywords || selectedCard?.keywords || []
: selectedCard?.keywords || [];
const meaning = selectedReversed
? selectedCard?.meaningReversed || selectedCard?.meaningUpright
: selectedCard?.meaningUpright || selectedCard?.meaningReversed;
const symbols = selectedCard?.symbols || [];
return ( return (
<aside className="tarot-panel"> <aside className="tarot-panel tarot-panel--reading">
<div className="tarot-panel__tabs">
<button type="button" className="is-active">카드 해석</button>
<button type="button">AI 인사이트</button>
</div>
{selectedCard && ( {selectedCard && (
<header className="tarot-panel__head"> <header className="tarot-panel__head">
<h3 className="tarot-panel__title">{selectedCard.name}</h3> <span className="tarot-panel__position">{selectedPosition || '선택한 카드'}</span>
<p className="tarot-panel__sub">{selectedCard.nameEn}</p> <h3 className="tarot-panel__title">
{selectedCard.name} <small>({selectedCard.nameEn}{selectedReversed ? ' · 역방향' : ''})</small>
<span aria-hidden> </span>
</h3>
<p className="tarot-panel__sub">핵심 키워드</p>
<div className="tarot-panel__chips"> <div className="tarot-panel__chips">
{(selectedCard.keywords || []).slice(0, 4).map((k) => ( {keywords.slice(0, 5).map((k) => (
<span key={k} className="tarot-chip">{k}</span> <span key={k} className="tarot-chip">{k}</span>
))} ))}
</div> </div>
</header> </header>
)} )}
{selectedCard && !interpretation && (
<>
<section className="tarot-panel__section">
<h4>카드 의미 요약</h4>
<p>{meaning}</p>
</section>
<section className="tarot-panel__section">
<h4>주요 상징</h4>
<ul className="tarot-symbol-list">
{(symbols.length ? symbols : keywords.slice(0, 4).map((keyword) => ({
label: keyword,
meaning: `${keyword}의 흐름을 관찰하세요.`,
}))).slice(0, 4).map((symbol) => (
<li key={symbol.label}>
<span aria-hidden></span>
<strong>{symbol.label}</strong>
{symbol.meaning}
</li>
))}
</ul>
</section>
<section className="tarot-panel__section tarot-panel__ai-note">
<h4>AI 타로 해석 </h4>
<p>모든 카드를 뽑은 해석 버튼을 누르면 질문과 스프레드 위치를 함께 반영한 리딩이 생성됩니다.</p>
</section>
<div className="tarot-panel__advice-card">
<span aria-hidden></span>
<p>
<strong>오늘의 조언</strong>
당신의 직감을 믿고, 작은 신호에도 기울이세요.
</p>
</div>
</>
)}
{cardDetail && ( {cardDetail && (
<section className="tarot-panel__section"> <section className="tarot-panel__section">
<h4> 위치의 해석</h4> <h4> 위치의 해석</h4>
@@ -61,6 +125,7 @@ export default function InterpretationPanel({ interpretation, selectedCard, focu
</section> </section>
)} )}
{interpretation && (
<section className="tarot-panel__section"> <section className="tarot-panel__section">
<h4>종합 해석 <ConfidenceBadge level={interpretation.confidence} /></h4> <h4>종합 해석 <ConfidenceBadge level={interpretation.confidence} /></h4>
<p>{interpretation.summary}</p> <p>{interpretation.summary}</p>
@@ -69,8 +134,9 @@ export default function InterpretationPanel({ interpretation, selectedCard, focu
<p className="tarot-panel__warning"> {interpretation.warning}</p> <p className="tarot-panel__warning"> {interpretation.warning}</p>
)} )}
</section> </section>
)}
{(interpretation.interactions || []).length > 0 && ( {(interpretation?.interactions || []).length > 0 && (
<section className="tarot-panel__section"> <section className="tarot-panel__section">
<h4>카드 상호작용</h4> <h4>카드 상호작용</h4>
<ul className="tarot-interactions"> <ul className="tarot-interactions">

View File

@@ -131,6 +131,489 @@ const SUIT_KEYWORDS = {
pentacles: { up: ['물질','일','안정','성취'], down: ['결핍','정체','집착'], theme: '물질과 일의 토대' }, pentacles: { up: ['물질','일','안정','성취'], down: ['결핍','정체','집착'], theme: '물질과 일의 토대' },
}; };
const MAJOR_DETAILS = {
'the-fool': {
keywords: ['새로운 시작', '자유', '도약', '순수한 신뢰', '미지의 여정'],
reversedKeywords: ['경솔함', '무모함', '준비 부족', '현실감 결여', '위험 신호 무시'],
meaningUpright: '바보는 아직 증명되지 않은 길 앞에서 마음을 여는 카드입니다. 완벽한 계획보다 첫걸음, 계산보다 경험, 안전한 반복보다 성장 가능성이 중요합니다. 질문의 핵심에는 새 출발, 관점 전환, 예전 방식에서 벗어나려는 욕구가 있으며, 지금은 모든 답을 알고 움직이기보다 길 위에서 배우는 태도가 필요합니다.',
meaningReversed: '역방향의 바보는 자유가 충동으로 흐르거나, 준비 없는 낙관이 위험을 키우는 상태를 말합니다. 반대로 실패가 두려워 출발을 계속 미루는 모습일 수도 있습니다. 지금 필요한 것은 모험 자체를 포기하는 것이 아니라 최소한의 기준, 정보, 책임을 갖춘 뒤 움직이는 것입니다.',
symbols: [
{ label: '절벽', meaning: '익숙한 세계와 미지의 세계 사이의 경계. 도약은 가능성이지만 동시에 주의가 필요합니다.' },
{ label: '작은 짐', meaning: '과거의 짐이 가볍다는 뜻입니다. 아직 고정된 역할이나 부담이 적어 새로운 선택이 열려 있습니다.' },
{ label: '흰 개', meaning: '본능, 동반자, 경고의 상징입니다. 순수한 충동을 따르되 위험 신호도 들어야 합니다.' },
{ label: '태양과 산', meaning: '높은 이상과 장기 여정. 지금의 시작은 당장 작아 보여도 큰 성장으로 이어질 수 있습니다.' },
],
},
'the-magician': {
keywords: ['의지', '실행력', '자원 활용', '집중', '현실화'],
reversedKeywords: ['조작', '산만함', '잠재력 낭비', '말뿐인 계획', '자기기만'],
meaningUpright: '마법사는 생각을 현실로 끌어내리는 능력의 카드입니다. 이미 필요한 도구와 재료는 상당 부분 갖춰져 있으며, 관건은 흩어진 에너지를 한 방향으로 모으는 것입니다. 질문이 일, 관계, 창작 어느 영역이든 지금은 기다림보다 명확한 의도, 설득, 기술, 실행 계획이 결과를 만듭니다.',
meaningReversed: '역방향의 마법사는 능력은 있으나 초점이 흐려져 결과가 나오지 않거나, 말과 이미지로 상황을 과장하는 모습을 경고합니다. 누군가의 능숙한 표현에 휘둘리고 있거나, 스스로도 준비보다 포장에 의존하고 있을 수 있습니다. 의도와 수단이 윤리적으로 일치하는지 확인해야 합니다.',
symbols: [
{ label: '네 가지 도구', meaning: '완드, 컵, 검, 펜타클은 의지·감정·사고·물질 자원을 뜻합니다. 필요한 요소가 이미 테이블 위에 있습니다.' },
{ label: '위와 아래를 잇는 손', meaning: '영감과 현실을 연결하는 행위. 생각을 구체적 행동으로 번역해야 함을 보여줍니다.' },
{ label: '무한대 표식', meaning: '지속 가능한 잠재력과 순환하는 에너지. 재능은 반복 사용될 때 힘이 됩니다.' },
{ label: '붉은 장미와 흰 백합', meaning: '욕망과 순수한 의도의 조화. 열망이 분명할수록 방향도 선명해집니다.' },
],
},
'the-high-priestess': {
keywords: ['직관', '침묵', '잠재의식', '비밀', '내면의 지혜'],
reversedKeywords: ['직관 무시', '정보 은폐', '불안한 추측', '내면 단절', '표면적 판단'],
meaningUpright: '여사제는 겉으로 드러난 사실보다 아직 말해지지 않은 진실에 주목하라는 카드입니다. 상황을 억지로 밀어붙이기보다 관찰, 침묵, 꿈, 반복되는 감각을 읽어야 합니다. 질문의 답은 외부의 큰 소리보다 내면의 조용한 확신, 숨겨진 정보, 아직 공개되지 않은 맥락 속에 있습니다.',
meaningReversed: '역방향의 여사제는 직관과 불안을 혼동하거나, 중요한 정보가 닫혀 있어 판단이 왜곡되는 상태입니다. 침묵이 지혜가 아니라 회피가 되었을 가능성도 있습니다. 감정적 추측을 사실처럼 단정하지 말고, 필요한 질문을 정확히 던져 확인하는 과정이 필요합니다.',
symbols: [
{ label: 'B와 J 기둥', meaning: '대립하는 힘 사이의 문턱. 흑백 판단을 넘어 중간의 숨은 질서를 보라는 뜻입니다.' },
{ label: '장막', meaning: '아직 드러나지 않은 영역. 모든 정보를 당장 볼 수 없으므로 성급한 결론을 피해야 합니다.' },
{ label: '두루마리', meaning: '비밀 지식과 기록. 이미 알고 있지만 의식적으로 읽지 않은 진실을 상징합니다.' },
{ label: '달', meaning: '무의식과 주기. 감정의 흐름과 타이밍이 중요한 단서가 됩니다.' },
],
},
'the-empress': {
keywords: ['풍요', '돌봄', '창조성', '감각', '성장'],
reversedKeywords: ['과보호', '의존', '창조 정체', '소진', '자기 돌봄 부족'],
meaningUpright: '여황제는 무언가를 억지로 밀어붙이기보다 잘 자랄 환경을 만드는 카드입니다. 관계에서는 따뜻한 수용과 애정, 일에서는 창작과 생산성, 재물에서는 자원이 서서히 불어나는 흐름을 뜻합니다. 몸의 감각, 안정된 생활 리듬, 자연스러운 즐거움이 문제 해결의 기반이 됩니다.',
meaningReversed: '역방향의 여황제는 돌봄이 지나쳐 의존을 만들거나, 타인을 챙기느라 자신의 에너지가 고갈된 상태입니다. 창조적 결과를 원하지만 몸과 마음의 토양이 말라 있을 수 있습니다. 주는 것과 받는 것의 균형, 휴식, 현실적인 자원 관리가 먼저입니다.',
symbols: [
{ label: '밀밭', meaning: '결실과 물질적 풍요. 지금의 노력은 가시적인 수확으로 이어질 가능성이 있습니다.' },
{ label: '석류 문양', meaning: '생명력, 다산, 창조의 씨앗. 아이디어나 관계가 무르익는 단계입니다.' },
{ label: '숲과 강', meaning: '감정과 자연 리듬. 성장은 통제보다 흐름을 탈 때 빨라집니다.' },
{ label: '비너스 표식', meaning: '사랑, 아름다움, 매력. 가치 있는 것을 부드럽게 끌어당기는 힘입니다.' },
],
},
'the-emperor': {
keywords: ['구조', '권위', '책임', '통제', '안정'],
reversedKeywords: ['강압', '경직', '권위 남용', '책임 회피', '통제 욕구'],
meaningUpright: '황제는 감정의 파도보다 구조와 원칙이 필요한 시점을 말합니다. 기준을 세우고, 역할을 분명히 하며, 장기적으로 유지될 수 있는 질서를 만들어야 합니다. 질문의 핵심이 관계라면 경계와 책임, 일이라면 리더십과 운영 능력, 재물이라면 안정적인 관리 체계를 뜻합니다.',
meaningReversed: '역방향의 황제는 질서가 지나쳐 압박이 되거나, 반대로 책임자가 부재해 기준이 무너진 상태를 가리킵니다. 누군가가 권위를 방패처럼 쓰고 있거나, 결정해야 할 사람이 결정을 피하고 있을 수 있습니다. 유연성을 잃지 않는 원칙이 필요합니다.',
symbols: [
{ label: '돌 왕좌', meaning: '견고한 기반과 현실적 권력. 감정보다 지속 가능한 구조가 중요합니다.' },
{ label: '양 머리 장식', meaning: '개척, 추진력, 지배적 의지. 빠른 결단과 리더십을 요구합니다.' },
{ label: '갑옷', meaning: '보호와 방어. 부드러운 대화보다 원칙을 지켜야 할 때가 있습니다.' },
{ label: '메마른 산', meaning: '쉽지 않은 환경에서도 버티는 힘. 편안함보다 책임이 우선될 수 있습니다.' },
],
},
'the-hierophant': {
keywords: ['전통', '가르침', '제도', '멘토', '공식화'],
reversedKeywords: ['관습 거부', '형식주의', '권위 의심', '독자 노선', '낡은 규칙'],
meaningUpright: '교황은 검증된 지식, 제도, 공동체의 규칙 안에서 길을 찾는 카드입니다. 혼자 추측하기보다 경험자에게 배우고, 절차를 따르고, 관계나 일을 공식화하는 것이 도움이 됩니다. 질문 속 상황은 개인적 감정만으로 해결되기보다 사회적 기준, 윤리, 약속, 신뢰의 틀 안에서 다뤄져야 합니다.',
meaningReversed: '역방향의 교황은 전통이 더 이상 답이 아니거나, 겉으로만 옳은 형식이 본질을 가리는 상태입니다. 권위자의 말이 실제 상황과 맞는지 점검해야 합니다. 규칙을 깨야 할 수도 있지만, 단순한 반항이 아니라 나만의 원칙을 세우는 것이 중요합니다.',
symbols: [
{ label: '두 제자', meaning: '배움과 전수. 혼자 해결하기보다 조언과 검증을 받아야 합니다.' },
{ label: '삼중관', meaning: '정신·도덕·사회적 권위. 개인 문제도 더 큰 기준과 연결되어 있습니다.' },
{ label: '열쇠', meaning: '숨은 문을 여는 지식. 올바른 절차나 핵심 정보를 찾는 것이 관건입니다.' },
{ label: '축복의 손', meaning: '승인과 의식. 관계나 계획을 공식화하는 흐름을 뜻합니다.' },
],
},
'the-lovers': {
keywords: ['사랑', '선택', '가치관', '결합', '정직한 끌림'],
reversedKeywords: ['불일치', '관계 갈등', '선택 회피', '가치 충돌', '유혹'],
meaningUpright: '연인은 단순한 로맨스보다 마음과 가치가 만나는 선택을 뜻합니다. 누군가와 연결되거나 어떤 길을 택할 때, 외부 조건보다 내가 진심으로 동의하는 가치가 기준이 되어야 합니다. 관계에서는 신뢰와 투명성, 일에서는 자신의 신념과 맞는 선택, 삶에서는 두 방향 중 하나를 의식적으로 고르는 순간을 나타냅니다.',
meaningReversed: '역방향의 연인은 마음과 행동이 어긋난 상태입니다. 끌림은 있지만 신뢰가 부족하거나, 선택을 미뤄 더 큰 혼란을 만들 수 있습니다. 관계의 문제라면 솔직한 대화가 필요하고, 선택의 문제라면 남이 기대하는 답과 내가 감당할 답을 분리해야 합니다.',
symbols: [
{ label: '두 인물', meaning: '서로 다른 자아나 두 선택지. 진정한 결합은 숨김없는 상태에서 가능합니다.' },
{ label: '천사', meaning: '높은 가치와 보호. 단순 욕망보다 더 큰 윤리와 의미가 개입합니다.' },
{ label: '나무와 뱀', meaning: '유혹, 지식, 선택의 책임. 알고도 선택하는 성숙함이 필요합니다.' },
{ label: '산', meaning: '관계와 선택이 넘어야 할 과제. 사랑만으로는 부족하고 의지가 필요합니다.' },
],
},
'the-chariot': {
keywords: ['전진', '승리', '의지', '자기 통제', '목표 집중'],
reversedKeywords: ['방향 상실', '과속', '통제 실패', '분산', '억지 추진'],
meaningUpright: '전차는 상반된 힘을 하나의 목표로 묶어 앞으로 나아가는 카드입니다. 갈등이 사라져서 움직이는 것이 아니라, 갈등을 통제하면서 전진합니다. 지금은 감정과 환경에 끌려가기보다 목적지를 분명히 하고, 규율과 결단으로 흐름을 잡아야 합니다.',
meaningReversed: '역방향의 전차는 속도는 있지만 방향이 없거나, 통제하려 할수록 상황이 더 흔들리는 상태입니다. 의지만으로 밀어붙이면 주변과 충돌할 수 있습니다. 목표, 수단, 감정의 균형을 다시 맞추고 무엇을 이기려 하는지보다 어디로 가려는지 확인해야 합니다.',
symbols: [
{ label: '두 스핑크스', meaning: '상반된 욕구와 방향. 둘 중 하나를 없애기보다 함께 끌고 가야 합니다.' },
{ label: '갑옷과 별 장식', meaning: '보호된 의지와 높은 목표. 사적인 감정보다 사명이 앞섭니다.' },
{ label: '성 밖의 길', meaning: '안전지대에서 벗어난 도전. 익숙한 환경을 떠나야 전진이 시작됩니다.' },
{ label: '고삐 없는 전차', meaning: '물리적 통제보다 정신적 집중이 핵심입니다.' },
],
},
strength: {
keywords: ['용기', '인내', '자기 조절', '부드러운 힘', '신뢰'],
reversedKeywords: ['자신감 저하', '감정 폭발', '힘의 남용', '두려움', '자제력 부족'],
meaningUpright: '힘 카드는 강압이 아니라 부드러운 통제와 내면의 용기를 말합니다. 문제를 제압하려 하기보다 신뢰와 인내로 다루면 더 큰 힘이 나옵니다. 관계에서는 상대를 밀어붙이지 않는 영향력, 일에서는 꾸준함, 자기 문제에서는 충동을 다스리는 성숙함을 뜻합니다.',
meaningReversed: '역방향의 힘은 감정을 억누르다 폭발하거나, 자신감 부족 때문에 필요 이상으로 방어적인 상태를 가리킵니다. 외부의 적보다 내 안의 두려움, 분노, 열등감이 핵심일 수 있습니다. 문제를 이기기 전에 자신을 진정시키는 과정이 먼저입니다.',
symbols: [
{ label: '사자', meaning: '본능, 욕망, 두려움. 없애야 할 것이 아니라 길들여야 할 에너지입니다.' },
{ label: '부드러운 손길', meaning: '강제보다 신뢰가 효과적임을 보여줍니다.' },
{ label: '무한대 표식', meaning: '지속적인 내면의 힘. 단발성 승부보다 꾸준한 자기 조절이 중요합니다.' },
{ label: '흰 옷', meaning: '순수한 의도. 힘은 목적이 맑을 때 가장 안정적으로 작동합니다.' },
],
},
'the-hermit': {
keywords: ['성찰', '고독', '탐구', '내면의 길', '지혜'],
reversedKeywords: ['고립', '회피', '외로움', '닫힌 마음', '조언 거부'],
meaningUpright: '은둔자는 외부의 소음에서 물러나 자신의 빛으로 길을 확인하는 카드입니다. 지금은 빠른 반응보다 깊은 숙고, 정보 수집, 혼자만의 정리가 필요합니다. 관계에서도 즉각적 답을 강요하기보다 거리를 두고 본질을 보아야 하고, 일에서는 전문가적 집중과 장기적 관점이 중요합니다.',
meaningReversed: '역방향의 은둔자는 필요한 고독이 지나쳐 단절이 되거나, 혼자 생각하는 시간이 회피로 변한 상태입니다. 누구의 말도 듣지 않으려 하거나, 도움을 요청해야 할 때 스스로를 가두고 있을 수 있습니다. 내면의 답과 외부 피드백의 균형이 필요합니다.',
symbols: [
{ label: '등불', meaning: '내면의 진실과 작은 단서. 길 전체가 아니라 다음 한 걸음을 비춥니다.' },
{ label: '지팡이', meaning: '경험으로 얻은 지지대. 오래 쌓은 지혜가 현재를 버티게 합니다.' },
{ label: '산 정상', meaning: '높은 관점과 고독. 멀리 보기 위해 잠시 떨어져 있어야 합니다.' },
{ label: '회색 망토', meaning: '외부 자극을 줄이고 본질을 가리는 색을 걷어내는 태도입니다.' },
],
},
'wheel-of-fortune': {
keywords: ['전환점', '운명', '순환', '기회', '흐름 변화'],
reversedKeywords: ['정체', '악순환', '타이밍 불일치', '통제 집착', '예상 밖 변수'],
meaningUpright: '운명의 수레바퀴는 상황의 축이 바뀌는 시점을 알립니다. 개인의 노력만으로 모든 것을 통제할 수는 없지만, 흐름을 읽고 타이밍에 맞춰 움직이면 큰 기회가 열립니다. 반복되던 패턴이 전환되며, 우연처럼 보이는 사건이 새로운 방향을 만들 수 있습니다.',
meaningReversed: '역방향의 수레바퀴는 변화가 지연되거나 같은 패턴을 반복하는 상태입니다. 흐름을 억지로 붙잡을수록 피로가 커질 수 있습니다. 지금은 불운을 탓하기보다 반복되는 선택 구조를 확인하고, 바꿀 수 있는 작은 행동부터 조정해야 합니다.',
symbols: [
{ label: '바퀴', meaning: '상승과 하강의 순환. 현재 상태는 고정된 운명이 아니라 움직이는 흐름입니다.' },
{ label: '네 생물', meaning: '안정된 원리와 네 방향. 변화 속에서도 기준이 필요합니다.' },
{ label: '스핑크스', meaning: '수수께끼와 균형. 모든 답을 모를 때도 중심을 잡아야 합니다.' },
{ label: '책과 문자', meaning: '배움과 기록. 반복되는 사건에서 패턴을 읽으라는 신호입니다.' },
],
},
justice: {
keywords: ['균형', '진실', '책임', '공정함', '원인과 결과'],
reversedKeywords: ['불공정', '책임 회피', '편향', '판단 오류', '불균형'],
meaningUpright: '정의는 감정적 호불호보다 사실, 균형, 책임을 요구합니다. 지금의 결과는 이전 선택의 누적과 연결되어 있으며, 앞으로의 결정도 명확한 기준을 가져야 합니다. 계약, 법적 문제, 관계의 공정성, 일의 평가에서 객관적 자료와 정직한 태도가 핵심입니다.',
meaningReversed: '역방향의 정의는 사실을 외면하거나 한쪽 입장만으로 판단하는 위험을 경고합니다. 누군가 책임을 회피하거나 불공정한 구조가 작동할 수 있습니다. 감정적 보복이나 자기합리화를 멈추고, 증거와 기준을 다시 확인해야 합니다.',
symbols: [
{ label: '저울', meaning: '균형과 비교. 선택의 양쪽 결과를 공정하게 재야 합니다.' },
{ label: '검', meaning: '명료한 판단과 결단. 애매함을 자르는 순간이 필요합니다.' },
{ label: '붉은 커튼', meaning: '숨은 동기와 결과의 무게. 표면 아래의 책임을 봐야 합니다.' },
{ label: '왕관', meaning: '원칙의 권위. 개인 감정보다 상위 기준이 작동합니다.' },
],
},
'the-hanged-man': {
keywords: ['멈춤', '관점 전환', '내려놓음', '수용', '희생의 의미'],
reversedKeywords: ['정체', '고집', '무의미한 희생', '회피', '관점 폐쇄'],
meaningUpright: '매달린 사람은 억지로 움직일수록 더 묶이는 상황에서 관점을 바꾸라고 말합니다. 지금의 지연은 실패가 아니라 다른 시각을 얻는 시간일 수 있습니다. 무언가를 내려놓거나 잠시 멈추면, 이전에는 보이지 않던 해법과 더 큰 의미가 드러납니다.',
meaningReversed: '역방향은 멈춤이 통찰로 이어지지 않고 고집이나 회피가 된 상태입니다. 희생하고 있다고 느끼지만 실제로는 변화해야 할 관점을 붙들고 있을 수 있습니다. 기다림을 계속할지, 매듭을 풀 행동을 할지 분명히 정해야 합니다.',
symbols: [
{ label: '거꾸로 매달린 자세', meaning: '세상을 반대로 보는 훈련. 기존 판단을 뒤집어야 답이 보입니다.' },
{ label: '평온한 얼굴', meaning: '수동적 패배가 아니라 의식적 수용을 뜻합니다.' },
{ label: '빛나는 머리', meaning: '멈춤 속에서 생기는 깨달음. 행동보다 통찰이 먼저입니다.' },
{ label: '한쪽 다리의 십자 형태', meaning: '제한 속에서도 내적 균형을 세우는 상징입니다.' },
],
},
death: {
keywords: ['종결', '전환', '놓아주기', '변형', '재생'],
reversedKeywords: ['변화 저항', '집착', '지연', '두려움', '미완의 끝맺음'],
meaningUpright: '죽음은 문자 그대로의 끝이 아니라 한 단계가 완전히 닫히고 다음 형태로 변하는 과정입니다. 더 이상 살릴 수 없는 방식, 관계, 역할, 기대를 놓아야 새 흐름이 들어옵니다. 질문의 핵심에는 손실보다 재편성이 있으며, 진짜 변화는 표면 수정이 아니라 구조의 전환입니다.',
meaningReversed: '역방향의 죽음은 끝난 것을 인정하지 못해 에너지가 묶인 상태입니다. 변화가 두려워 낡은 구조를 붙잡으면 자연스러운 재생도 지연됩니다. 무엇을 잃는지보다 무엇이 이미 생명력을 다했는지 정확히 봐야 합니다.',
symbols: [
{ label: '흰 말', meaning: '정화된 변화의 힘. 끝남은 파괴만이 아니라 새로운 질서의 도착입니다.' },
{ label: '검은 갑옷', meaning: '피할 수 없는 전환. 감정과 상관없이 진행되는 변화가 있습니다.' },
{ label: '떠오르는 태양', meaning: '끝 뒤의 시작. 상실 이후에도 다음 국면은 열립니다.' },
{ label: '쓰러진 왕과 기도하는 인물', meaning: '지위와 태도에 상관없이 모두 변화 앞에 서게 됨을 보여줍니다.' },
],
},
temperance: {
keywords: ['절제', '조화', '치유', '중용', '통합'],
reversedKeywords: ['불균형', '과잉', '조급함', '극단', '조율 실패'],
meaningUpright: '절제는 서로 다른 요소를 천천히 섞어 새로운 균형을 만드는 카드입니다. 지금은 극단적 선택보다 조율, 회복, 적당한 속도가 중요합니다. 관계에서는 차이를 부드럽게 맞추고, 일에서는 자원과 시간을 균형 있게 배분하며, 마음에서는 치유가 진행됩니다.',
meaningReversed: '역방향의 절제는 한쪽으로 치우친 상태입니다. 너무 빨리 결론을 내리거나, 감정·일·소비·관계에서 과잉이 생겼을 수 있습니다. 해결은 더 강한 자극이 아니라 리듬을 되찾고 섞이지 않는 요소를 다시 분리해 조정하는 것입니다.',
symbols: [
{ label: '두 컵 사이의 물', meaning: '감정과 에너지의 교환. 서로 다른 것을 천천히 조화시키는 과정입니다.' },
{ label: '한 발은 물, 한 발은 땅', meaning: '감정과 현실의 균형. 직관과 실용을 함께 써야 합니다.' },
{ label: '천사', meaning: '치유와 보호. 조급하지 않은 회복의 흐름이 있습니다.' },
{ label: '길과 산', meaning: '장기적 목표로 이어지는 중간 과정. 지금의 조율은 다음 단계의 준비입니다.' },
],
},
'the-devil': {
keywords: ['집착', '속박', '욕망', '그림자', '중독적 패턴'],
reversedKeywords: ['해방', '자각', '단절', '회복', '속박 풀림'],
meaningUpright: '악마는 외부의 운명보다 스스로 반복하는 집착과 의존을 직시하게 합니다. 관계에서는 통제와 욕망, 일에서는 물질적 유혹이나 과도한 야망, 심리적으로는 부정하고 싶은 그림자가 드러납니다. 이 카드는 죄책감보다 자각을 요구하며, 묶인 사슬이 실제보다 느슨할 수 있음을 보여줍니다.',
meaningReversed: '역방향의 악마는 속박을 알아차리고 풀어내는 단계입니다. 중독적 관계, 나쁜 습관, 물질적 집착에서 벗어날 기회가 있습니다. 다만 해방은 선언만으로 오지 않으므로 유혹을 반복시키는 구조를 실제로 바꿔야 합니다.',
symbols: [
{ label: '느슨한 사슬', meaning: '벗어날 수 있지만 익숙해서 머무는 패턴. 선택의 책임을 묻습니다.' },
{ label: '염소 형상', meaning: '본능, 욕망, 금기. 억압된 욕구가 왜곡되어 나타날 수 있습니다.' },
{ label: '횃불', meaning: '아래로 향한 에너지. 창조적 힘이 파괴적 습관으로 흐르는 상태입니다.' },
{ label: '나체의 두 인물', meaning: '취약성과 의존. 관계나 욕망 앞에서 경계가 약해질 수 있습니다.' },
],
},
'the-tower': {
keywords: ['붕괴', '각성', '급변', '진실 노출', '해방적 충격'],
reversedKeywords: ['붕괴 회피', '지연된 변화', '내부 균열', '두려움', '경고 무시'],
meaningUpright: '탑은 더 이상 유지될 수 없는 구조가 갑자기 무너지는 카드입니다. 충격은 크지만, 거짓 기반이나 억눌린 진실을 드러내어 새로운 현실을 만들 공간을 냅니다. 관계, 일, 믿음 체계 어디에서든 지금 필요한 것은 무너짐을 막는 척이 아니라 실제 균열을 인정하는 것입니다.',
meaningReversed: '역방향의 탑은 변화의 조짐을 이미 알면서도 외면하는 상태입니다. 겉으로는 큰 사고가 없지만 내부 압력은 쌓이고 있을 수 있습니다. 작은 수정으로 끝낼 수 있을 때 구조를 점검해야 하며, 진실을 미룰수록 충격은 커집니다.',
symbols: [
{ label: '번개', meaning: '갑작스러운 깨달음과 외부 충격. 숨겨진 사실이 순식간에 드러납니다.' },
{ label: '무너지는 왕관', meaning: '잘못된 자부심이나 권위의 붕괴. 통제 환상이 깨집니다.' },
{ label: '떨어지는 인물', meaning: '안전하다고 믿던 위치에서 내려오는 경험. 현실을 다시 배워야 합니다.' },
{ label: '불꽃', meaning: '파괴와 정화. 불편한 진실이 낡은 구조를 태웁니다.' },
],
},
'the-star': {
keywords: ['희망', '치유', '영감', '신뢰', '장기 비전'],
reversedKeywords: ['낙담', '자기 의심', '단절감', '희망 상실', '회복 지연'],
meaningUpright: '별은 큰 혼란이 지나간 뒤 회복되는 빛입니다. 당장 결과가 완성되지 않아도 방향은 밝아지고, 마음은 다시 신뢰를 배웁니다. 치유, 영감, 창작, 장기 목표에 유리하며, 자신을 있는 그대로 드러낼수록 필요한 도움과 흐름이 찾아옵니다.',
meaningReversed: '역방향의 별은 희망이 약해지고 자신을 믿기 어려운 상태입니다. 주변의 좋은 신호를 보지 못하거나, 회복이 늦다고 느낄 수 있습니다. 큰 확신을 억지로 만들기보다 작은 회복의 증거를 기록하고 몸과 마음을 다시 연결해야 합니다.',
symbols: [
{ label: '큰 별과 작은 별들', meaning: '중심 비전과 여러 가능성. 한 가지 희망이 여러 길을 비춥니다.' },
{ label: '물 붓기', meaning: '감정의 치유와 현실의 돌봄. 내면과 외부 세계를 함께 회복해야 합니다.' },
{ label: '나체의 인물', meaning: '진정성, 취약함, 꾸밈없는 자기. 숨김이 줄수록 치유가 빠릅니다.' },
{ label: '새', meaning: '영감과 소식. 높은 관점에서 새로운 메시지가 도착합니다.' },
],
},
'the-moon': {
keywords: ['불확실성', '무의식', '환상', '직관', '감정의 안개'],
reversedKeywords: ['혼란 해소', '진실 드러남', '불안 완화', '자기기만 자각', '직관 회복'],
meaningUpright: '달은 사실과 상상이 섞여 시야가 흐린 상태를 말합니다. 지금 보이는 것만으로 단정하기 어렵고, 꿈·불안·직관·반복되는 감정이 중요한 단서가 됩니다. 숨겨진 동기나 오해가 있을 수 있으므로 결론을 서두르지 말고, 감정의 안개가 걷힐 시간을 두어야 합니다.',
meaningReversed: '역방향의 달은 혼란이 서서히 풀리거나, 자신을 속이던 패턴을 알아차리는 단계입니다. 불안은 여전히 남아 있어도 진실에 가까워지고 있습니다. 다만 새로 드러난 단서를 왜곡하지 않도록 차분한 확인이 필요합니다.',
symbols: [
{ label: '달빛', meaning: '부분적으로만 보이는 진실. 밝지만 태양처럼 명확하지는 않습니다.' },
{ label: '개와 늑대', meaning: '길들여진 자아와 야생의 본능. 이성보다 본능이 크게 반응할 수 있습니다.' },
{ label: '가재', meaning: '무의식에서 올라오는 감정. 오래 묻어둔 불안이나 욕구가 표면화됩니다.' },
{ label: '두 탑 사이의 길', meaning: '불확실한 통과 의례. 두려움을 지나야 다음 세계로 갑니다.' },
],
},
'the-sun': {
keywords: ['명료성', '성공', '기쁨', '활력', '공개'],
reversedKeywords: ['과신', '일시적 지연', '피상적 낙관', '기쁨 차단', '자만'],
meaningUpright: '태양은 숨겨진 것이 밝게 드러나고 생명력이 회복되는 카드입니다. 관계에서는 솔직함과 따뜻함, 일에서는 성과와 인정, 개인적으로는 자신감과 건강한 자기표현을 뜻합니다. 복잡했던 문제가 단순해지고, 긍정적 결과를 공개적으로 누릴 수 있습니다.',
meaningReversed: '역방향의 태양은 좋은 흐름이 있지만 충분히 받아들이지 못하거나, 지나친 자신감으로 세부를 놓치는 상태입니다. 성공이 늦어질 수는 있으나 완전히 사라진 것은 아닙니다. 기쁨을 현실적인 계획과 연결하면 흐름을 회복할 수 있습니다.',
symbols: [
{ label: '태양', meaning: '명료한 의식과 생명력. 숨겨진 것이 드러나고 에너지가 회복됩니다.' },
{ label: '아이', meaning: '순수한 기쁨과 자유로운 표현. 복잡함보다 단순함이 힘입니다.' },
{ label: '흰 말', meaning: '순수한 추진력. 억지 통제가 아니라 자연스러운 전진입니다.' },
{ label: '해바라기', meaning: '성장과 성과. 빛을 받은 노력이 결실로 나타납니다.' },
],
},
judgement: {
keywords: ['각성', '부름', '재평가', '결단', '새 단계'],
reversedKeywords: ['부름 회피', '자기비판', '과거 집착', '결단 지연', '변화 두려움'],
meaningUpright: '심판은 과거를 정산하고 더 큰 부름에 응답하는 카드입니다. 이전 선택의 의미가 분명해지고, 이제는 같은 패턴을 반복할지 새 단계로 올라설지 결정해야 합니다. 용서, 재평가, 합격·판정, 중요한 전환점과 연결되며 자기 삶을 더 넓은 관점에서 바라보게 합니다.',
meaningReversed: '역방향의 심판은 이미 들은 내면의 부름을 무시하거나, 과거의 후회 때문에 앞으로 나아가지 못하는 상태입니다. 자신을 지나치게 심판하면 변화의 문도 닫힙니다. 실수의 의미를 배움으로 전환하고 결정을 미루는 이유를 직면해야 합니다.',
symbols: [
{ label: '나팔', meaning: '깨어나라는 신호. 더 이상 예전 방식으로 잠들어 있을 수 없습니다.' },
{ label: '무덤에서 일어나는 인물', meaning: '재생과 부활. 끝났다고 여긴 가능성이 다시 살아납니다.' },
{ label: '천사', meaning: '상위의 부름과 평가. 개인적 판단을 넘어 삶의 방향성이 개입합니다.' },
{ label: '벌거벗은 몸', meaning: '숨김없는 자기 평가. 진실 앞에서 변명보다 수용이 필요합니다.' },
],
},
'the-world': {
keywords: ['완성', '통합', '성취', '순환의 마무리', '전체성'],
reversedKeywords: ['미완성', '마무리 지연', '닫히지 않은 과제', '성취감 부족', '반복'],
meaningUpright: '세계는 한 주기의 완성과 통합을 나타냅니다. 흩어져 있던 경험이 하나의 의미로 묶이고, 노력의 결과를 인정받거나 다음 단계로 넘어갈 준비가 됩니다. 관계에서는 성숙한 연결, 일에서는 프로젝트 완수, 삶에서는 자신이 지나온 길을 온전하게 받아들이는 시점입니다.',
meaningReversed: '역방향의 세계는 거의 끝났지만 마지막 매듭이 남아 있거나, 성취를 했음에도 스스로 인정하지 못하는 상태입니다. 반복되는 과제를 마무리하지 않으면 다음 장으로 넘어가기 어렵습니다. 완벽함보다 완료를 선택해야 할 수 있습니다.',
symbols: [
{ label: '월계관', meaning: '완성과 승리. 하나의 순환이 닫히고 새로운 순환의 문이 열립니다.' },
{ label: '춤추는 인물', meaning: '자유로운 통합. 몸과 마음, 경험과 의미가 조화를 이룹니다.' },
{ label: '네 생물', meaning: '네 원소와 네 방향의 균형. 삶의 여러 영역이 하나로 정리됩니다.' },
{ label: '두 지팡이', meaning: '균형 잡힌 창조력. 이전 단계에서 얻은 힘을 다음 세계로 가져갑니다.' },
],
},
};
const SUIT_DETAILS = {
wands: {
domain: '열정, 창조성, 일의 추진력, 생명력, 목표를 향한 행동',
elementMeaning: '불 원소의 확장성. 빠르게 타오르는 의욕과 경쟁심, 영감이 행동으로 옮겨지는 방식을 보여줍니다.',
symbolLabels: ['싹 난 지팡이', '사막과 언덕', '불 원소'],
symbolMeanings: ['살아 있는 의지와 성장 가능성', '도전적 환경에서도 길을 여는 추진력', '영감, 욕망, 빠른 전개'],
},
cups: {
domain: '감정, 관계, 사랑, 직관, 기억과 치유',
elementMeaning: '물 원소의 수용성. 관계의 흐름, 감정의 깊이, 꿈과 상상, 마음의 회복을 읽습니다.',
symbolLabels: ['성배', '물', '물고기와 구름'],
symbolMeanings: ['감정을 담는 그릇과 마음의 교류', '무의식과 정서의 흐름', '직관적 메시지와 상상력'],
},
swords: {
domain: '생각, 말, 판단, 갈등, 진실을 가르는 힘',
elementMeaning: '공기 원소의 명료성. 분석, 의사소통, 결정, 불안과 갈등이 어떻게 작동하는지 보여줍니다.',
symbolLabels: ['검', '바람과 구름', '새벽의 빛'],
symbolMeanings: ['진실을 자르는 판단력과 말의 힘', '생각의 변화와 불안정한 정신 흐름', '고통 뒤에 오는 명료성'],
},
pentacles: {
domain: '돈, 일, 몸, 건강, 생활 기반, 현실적 성취',
elementMeaning: '땅 원소의 구체성. 자원, 노동, 습관, 물질적 안정과 장기적 결과를 다룹니다.',
symbolLabels: ['펜타클', '정원과 도시', '손과 도구'],
symbolMeanings: ['현실 세계의 가치와 자원', '시간을 들여 가꾸는 기반', '기술, 노동, 반복의 힘'],
},
};
const RANK_DETAILS = {
1: {
keywords: ['새 씨앗', '기회', '순수한 가능성', '시작의 힘', '선물'],
reversedKeywords: ['기회 지연', '잠재력 낭비', '시작 불안', '준비 부족'],
upright: '새로운 가능성이 처음 모습을 드러내는 카드입니다. 아직 결과는 작지만, 이 씨앗은 제대로 다루면 큰 성장으로 이어질 수 있습니다.',
reversed: '기회는 있지만 아직 형태가 불안정하거나, 시작할 준비가 부족한 상태입니다. 가능성을 현실로 만들 구체적 첫 행동이 필요합니다.',
symbols: [{ label: '구름 속의 손', meaning: '외부에서 들어오는 기회와 아직 인간의 손에 완전히 들어오지 않은 가능성입니다.' }],
},
2: {
keywords: ['선택', '균형', '초기 조율', '두 방향', '관계 설정'],
reversedKeywords: ['우유부단', '불균형', '관계 긴장', '선택 회피'],
upright: '두 흐름 사이에서 균형을 잡고 방향을 정하는 단계입니다. 아직 작지만 중요한 결정이 이후 전체 흐름을 바꿉니다.',
reversed: '선택을 미루거나 양쪽을 모두 잡으려다 균형이 무너질 수 있습니다. 핵심 기준을 세우는 것이 먼저입니다.',
symbols: [{ label: '둘의 구도', meaning: '대립이 아니라 조율의 가능성입니다. 두 요소가 어떤 방식으로 만나는지가 중요합니다.' }],
},
3: {
keywords: ['확장', '협력', '초기 성과', '표현', '성장'],
reversedKeywords: ['협업 불일치', '성장 지연', '분산', '기대와 현실 차이'],
upright: '처음의 선택이 외부 세계로 확장되는 단계입니다. 협력, 표현, 초기 결과가 나타나며 다음 가능성을 확인합니다.',
reversed: '확장하려는 힘은 있지만 호흡이 맞지 않거나 목표가 분산되어 있습니다. 역할과 기대치를 다시 맞춰야 합니다.',
symbols: [{ label: '세 요소의 삼각형', meaning: '관계와 구조가 안정되기 시작하는 첫 형태입니다. 혼자보다 함께 만들 때 힘이 커집니다.' }],
},
4: {
keywords: ['안정', '기반', '휴식', '구조화', '경계'],
reversedKeywords: ['정체', '불안정', '닫힘', '안주', '휴식 부족'],
upright: '기반을 세우고 에너지를 안정시키는 카드입니다. 급히 확장하기보다 지금까지 만든 구조를 다지는 데 의미가 있습니다.',
reversed: '안정이 정체가 되거나, 충분히 쉬지 못해 기반이 흔들릴 수 있습니다. 닫힌 구조를 조금 열어야 합니다.',
symbols: [{ label: '네 모서리', meaning: '집, 몸, 계획의 기본 구조. 움직임보다 안정성이 우선되는 시기입니다.' }],
},
5: {
keywords: ['갈등', '손실', '변화 압력', '불편한 성장', '시험'],
reversedKeywords: ['갈등 회피', '회복 시작', '분쟁 완화', '내부 소진'],
upright: '안정이 깨지고 문제의식이 드러나는 단계입니다. 불편하지만 이 충돌은 무엇을 고쳐야 하는지 보여줍니다.',
reversed: '갈등이 줄어들거나 회복이 시작되지만, 아직 상처나 피로가 남아 있습니다. 문제를 덮기보다 정리해야 합니다.',
symbols: [{ label: '흐트러진 중심', meaning: '예전 질서가 더는 충분하지 않다는 신호입니다. 손실 속에서도 배울 지점이 있습니다.' }],
},
6: {
keywords: ['회복', '조화', '교환', '인정', '이동'],
reversedKeywords: ['불공정한 교환', '과거 집착', '인정 결핍', '회복 지연'],
upright: '갈등 뒤 균형이 회복되고 도움, 인정, 이동이 생기는 단계입니다. 관계와 상황이 조금 더 부드러워집니다.',
reversed: '겉보기에는 회복처럼 보여도 교환이 불균형하거나 과거에 묶여 있을 수 있습니다. 진짜 균형인지 확인해야 합니다.',
symbols: [{ label: '주고받는 흐름', meaning: '도움, 인정, 기억, 이동처럼 에너지가 한쪽에서 다른 쪽으로 흐릅니다.' }],
},
7: {
keywords: ['평가', '선택지', '방어', '전략', '내면 시험'],
reversedKeywords: ['혼란', '포기 충동', '과잉 방어', '현실 회피'],
upright: '여러 가능성 앞에서 기준을 세우고 자신이 지킬 것을 선택하는 단계입니다. 전략과 분별력이 필요합니다.',
reversed: '가능성이 너무 많아 흐려지거나, 방어가 지나쳐 기회를 막을 수 있습니다. 환상과 현실을 분리해야 합니다.',
symbols: [{ label: '높은 지점과 여러 대상', meaning: '상황 전체를 보려는 노력. 무엇을 선택하고 무엇을 지킬지 시험받습니다.' }],
},
8: {
keywords: ['움직임', '전념', '반복', '진행', '탈피'],
reversedKeywords: ['지연', '강박', '속도 문제', '방향성 부족'],
upright: '에너지가 실제 움직임으로 전환되는 단계입니다. 반복, 기술, 빠른 진행 또는 더 깊은 의미를 향한 이동을 뜻합니다.',
reversed: '움직이고 있지만 효율이 낮거나 방향이 맞지 않을 수 있습니다. 속도를 조절하고 반복의 목적을 점검해야 합니다.',
symbols: [{ label: '반복되는 여덟 요소', meaning: '같은 행동이 누적되어 실력, 변화, 이동을 만듭니다.' }],
},
9: {
keywords: ['완성 직전', '내적 성숙', '만족', '인내', '회복력'],
reversedKeywords: ['불만족', '소진', '방어 과잉', '고립된 성취'],
upright: '긴 과정을 지나 거의 완성에 가까워진 상태입니다. 경험에서 나온 성숙함, 만족, 버티는 힘이 핵심입니다.',
reversed: '성과가 있어도 만족하지 못하거나, 끝까지 버티느라 소진되었을 수 있습니다. 자신을 돌보며 마무리해야 합니다.',
symbols: [{ label: '홀로 선 인물', meaning: '스스로 쌓은 경험과 독립성. 성취와 고립이 함께 나타날 수 있습니다.' }],
},
10: {
keywords: ['완성', '결과', '부담', '가족과 공동체', '다음 주기'],
reversedKeywords: ['과부하', '무너지는 구조', '불완전한 마무리', '책임 재분배'],
upright: '하나의 주기가 완성되어 결과가 현실화되는 단계입니다. 성취와 부담이 함께 오며, 다음 순환을 준비해야 합니다.',
reversed: '완성 직전의 부담이 지나치거나, 결과를 유지하기 어려운 구조일 수 있습니다. 책임을 나누고 마무리 방식을 조정해야 합니다.',
symbols: [{ label: '가득 찬 열 요소', meaning: '충만함과 포화 상태. 얻은 것이 많을수록 관리해야 할 것도 많습니다.' }],
},
11: {
keywords: ['메신저', '호기심', '초보자의 배움', '가능성 탐색', '새 소식'],
reversedKeywords: ['미숙함', '산만함', '소식 지연', '현실감 부족'],
upright: '시종은 해당 슈트의 에너지를 처음 배우는 사람입니다. 메시지, 새로운 관심, 실험, 호기심이 상황을 엽니다.',
reversed: '배우려는 마음은 있지만 미숙하거나 쉽게 산만해질 수 있습니다. 작은 책임부터 정확히 수행해야 합니다.',
symbols: [{ label: '젊은 인물', meaning: '새로운 태도와 배움의 시작. 아직 능숙하지 않지만 가능성이 큽니다.' }],
},
12: {
keywords: ['행동가', '추진', '탐색', '변화', '목표 추격'],
reversedKeywords: ['충동', '불안정', '과속', '방향 전환', '성급함'],
upright: '기사는 해당 슈트의 에너지를 적극적으로 움직입니다. 제안, 이동, 추격, 빠른 변화가 나타납니다.',
reversed: '추진력이 지나쳐 불안정하거나, 마음이 자주 바뀔 수 있습니다. 속도보다 방향과 책임이 중요합니다.',
symbols: [{ label: '말', meaning: '움직임과 추진력. 에너지가 머무르지 않고 외부로 나아갑니다.' }],
},
13: {
keywords: ['성숙한 수용', '돌봄', '직관적 운영', '내적 권위', '안정된 영향력'],
reversedKeywords: ['과잉 돌봄', '감정적 흔들림', '의존', '자기 돌봄 부족'],
upright: '여왕은 해당 슈트의 에너지를 내면화하고 돌봄과 감각으로 운영합니다. 부드럽지만 강한 영향력을 뜻합니다.',
reversed: '타인을 챙기느라 자신을 잃거나, 감정과 욕구가 과잉될 수 있습니다. 내면의 균형을 먼저 회복해야 합니다.',
symbols: [{ label: '왕좌의 여왕', meaning: '받아들이고 가꾸는 힘. 외적 지배보다 내적 안정과 영향력이 큽니다.' }],
},
14: {
keywords: ['숙련된 통제', '리더십', '책임', '현실화', '전략적 운영'],
reversedKeywords: ['권위 남용', '경직', '통제 과잉', '책임 회피'],
upright: '왕은 해당 슈트의 에너지를 사회적 책임과 성과로 구현합니다. 리더십, 판단, 장기 운영 능력이 핵심입니다.',
reversed: '능력은 있지만 경직되거나 통제적일 수 있습니다. 책임 있는 리더십인지, 단순한 지배 욕구인지 점검해야 합니다.',
symbols: [{ label: '왕좌의 왕', meaning: '성숙한 지배와 책임. 에너지를 외부 세계에서 결과로 만드는 힘입니다.' }],
},
};
const CARD_LENSES = {
wands: {
1: ['영감의 불씨', '창업/프로젝트 시작', '강한 의욕'],
2: ['세계관 확장', '계획 수립', '기다리는 선택'],
3: ['사업 확장', '협력의 성과', '먼 곳의 가능성'],
4: ['축하', '안정된 기반', '공동체의 기쁨'],
5: ['경쟁', '의견 충돌', '훈련을 통한 성장'],
6: ['승리', '인정', '리더로 보이는 순간'],
7: ['방어', '입장 고수', '불리한 상황에서의 용기'],
8: ['빠른 전개', '연락', '지체 없는 이동'],
9: ['버티는 힘', '경계심', '마지막 시험'],
10: ['과부하', '책임의 무게', '혼자 짊어진 일'],
11: ['새 아이디어', '모험심', '창조적 소식'],
12: ['돌진', '열정적 이동', '성급한 추진'],
13: ['카리스마', '자기표현', '따뜻한 리더십'],
14: ['비전 리더', '사업 감각', '확신 있는 추진'],
},
cups: {
1: ['감정의 시작', '사랑의 문', '치유의 물'],
2: ['상호 호감', '파트너십', '마음의 교환'],
3: ['우정', '축하', '창조적 협업'],
4: ['권태', '재평가', '내면으로 물러남'],
5: ['상실감', '후회', '남은 가능성'],
6: ['추억', '순수함', '과거와의 화해'],
7: ['환상', '선택지', '욕망의 그림자'],
8: ['떠남', '의미 탐색', '정서적 탈피'],
9: ['만족', '소원 성취', '감정적 풍요'],
10: ['가족적 행복', '정서적 완성', '관계의 조화'],
11: ['감성적 메시지', '상상력', '부드러운 제안'],
12: ['로맨스', '이상주의', '마음을 따르는 이동'],
13: ['깊은 공감', '직관적 돌봄', '정서적 안정'],
14: ['감정의 통제', '성숙한 사랑', '침착한 조언'],
},
swords: {
1: ['명료한 판단', '진실의 시작', '결정적 아이디어'],
2: ['교착', '판단 보류', '감정 차단'],
3: ['상처', '슬픔', '진실이 찌르는 순간'],
4: ['휴식', '회복', '생각의 정리'],
5: ['불편한 승리', '말의 상처', '갈등의 후유증'],
6: ['이동', '회복의 여정', '더 나은 곳으로 건너감'],
7: ['전략', '회피', '혼자 처리하려는 태도'],
8: ['제한감', '두려움', '스스로 만든 감옥'],
9: ['불안', '악몽', '죄책감'],
10: ['끝난 싸움', '완전한 종료', '바닥을 친 뒤의 새벽'],
11: ['정보 수집', '예리한 호기심', '경계하는 말'],
12: ['돌진하는 논리', '급한 결정', '논쟁'],
13: ['분별력', '독립적 판단', '솔직한 경계'],
14: ['전략적 사고', '냉철한 결정', '지적 권위'],
},
pentacles: {
1: ['현실적 기회', '돈의 씨앗', '건강한 기반'],
2: ['자원 균형', '멀티태스킹', '변동 관리'],
3: ['기술 협업', '인정받는 실력', '전문성'],
4: ['소유와 경계', '안정 집착', '재정 방어'],
5: ['결핍감', '소외', '도움 요청'],
6: ['나눔', '공정한 교환', '지원과 보상'],
7: ['기다림', '투자 평가', '장기 성과'],
8: ['훈련', '장인정신', '반복 노동'],
9: ['자립', '품격', '물질적 여유'],
10: ['가문/조직', '상속과 장기 안정', '완성된 기반'],
11: ['공부', '현실적 제안', '기술 습득'],
12: ['꾸준함', '책임감', '느리지만 확실한 전진'],
13: ['생활 감각', '돌봄과 풍요', '실용적 지혜'],
14: ['성공한 운영자', '재정 리더십', '안정된 성취'],
},
};
function buildMinorDetails(suit, rank, krName) {
const suitDetail = SUIT_DETAILS[suit];
const rankDetail = RANK_DETAILS[rank];
const lens = CARD_LENSES[suit][rank];
const keywords = [...lens, ...rankDetail.keywords.slice(0, 3)];
const reversedKeywords = [...rankDetail.reversedKeywords, `${SUIT_NAMES_EN[suit]} 에너지 불균형`];
return {
keywords,
reversedKeywords,
meaningUpright: `${krName}${suitDetail.domain}의 영역에서 ${lens.join(', ')}을(를) 보여줍니다. ${rankDetail.upright} 이 카드는 ${suitDetail.elementMeaning} 질문의 맥락에서는 지금 무엇이 자라고, 어디에 에너지를 쏟아야 하며, 어떤 현실적 행동이 다음 변화를 만드는지 읽게 합니다.`,
meaningReversed: `${krName} 역방향은 ${lens[0]}의 흐름이 막히거나 과장된 상태입니다. ${rankDetail.reversed} ${suitDetail.domain} 안에서 반복되는 불균형을 점검하고, 감정적 반응보다 실제로 조정 가능한 선택을 찾아야 합니다.`,
symbols: [
{ label: suitDetail.symbolLabels[0], meaning: suitDetail.symbolMeanings[0] },
{ label: suitDetail.symbolLabels[1], meaning: suitDetail.symbolMeanings[1] },
{ label: rankDetail.symbols[0].label, meaning: rankDetail.symbols[0].meaning },
{ label: lens[0], meaning: `${krName}의 핵심 장면입니다. 이 상징은 현재 질문에서 ${lens.join(', ')}이 어떻게 드러나는지 살피게 합니다.` },
],
};
}
function buildMinor() { function buildMinor() {
const out = []; const out = [];
let id = 22; let id = 22;
@@ -138,7 +621,7 @@ function buildMinor() {
for (let rank = 1; rank <= 14; rank++) { for (let rank = 1; rank <= 14; rank++) {
const krName = `${kr} ${RANK_NAMES[rank - 1]}`; const krName = `${kr} ${RANK_NAMES[rank - 1]}`;
const enName = `${RANK_EN[rank - 1]} of ${SUIT_NAMES_EN[suit]}`; const enName = `${RANK_EN[rank - 1]} of ${SUIT_NAMES_EN[suit]}`;
const kw = SUIT_KEYWORDS[suit]; const details = buildMinorDetails(suit, rank, krName);
out.push({ out.push({
id: id++, id: id++,
slug: `${RANK_EN[rank - 1].toLowerCase()}-of-${suit}`, slug: `${RANK_EN[rank - 1].toLowerCase()}-of-${suit}`,
@@ -148,10 +631,7 @@ function buildMinor() {
suit, suit,
rank, rank,
element, element,
keywords: [...kw.up, `${kr} ${rank}의 단계`], ...details,
reversedKeywords: [...kw.down, `${kr} ${rank} 정체`],
meaningUpright: `${kw.theme}${krName} 단계. ${kw.up.join(', ')} 의 흐름이 작동하는 시점.`,
meaningReversed: `${kr} 흐름의 정체 또는 왜곡. ${kw.down.join(', ')} 양상이 드러남.`,
}); });
} }
} }
@@ -161,6 +641,7 @@ function buildMinor() {
export const TAROT_DECK = [ export const TAROT_DECK = [
...MAJOR_ARCANA.map((c) => ({ ...MAJOR_ARCANA.map((c) => ({
...c, ...c,
...(MAJOR_DETAILS[c.slug] || {}),
arcana: 'major', arcana: 'major',
image: cardImage(c.slug), image: cardImage(c.slug),
})), })),