refactor(phase2.5): SajuFortuneSection 중복 Glyph 제거 — SajuIcons 재사용
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { LottoIcon } from './SajuIcons';
|
import { LottoIcon, SajuIcon } from './SajuIcons';
|
||||||
|
import type { SajuIconName } from './SajuIcons';
|
||||||
|
|
||||||
// ── 천간 / 지지 ───────────────────────────────────────────────────────
|
// ── 천간 / 지지 ───────────────────────────────────────────────────────
|
||||||
const STEMS = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
|
const STEMS = ['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
|
||||||
@@ -74,22 +75,30 @@ function seededRand(seed: number) {
|
|||||||
return () => { s = (s * 1664525 + 1013904223) & 0xffffffff; return (s >>> 0) / 0xffffffff; };
|
return () => { s = (s * 1664525 + 1013904223) & 0xffffffff; return (s >>> 0) / 0xffffffff; };
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 로컬 라인 아이콘 (이모지 대체, 파일 전용 — SajuIcons.tsx 미변경) ──────
|
// ── 로컬 라인 아이콘 (이모지 대체, 파일 전용) ──────────────────────────
|
||||||
type GlyphName = 'sun' | 'great' | 'good' | 'neutral' | 'caution' | 'money' | 'love' | 'career' | 'health' | 'social';
|
// SajuIcons.tsx와 도형이 겹치는 5종(great/money/love/career/health)은 SajuIcon을
|
||||||
|
// 재사용하고, 로컬 Glyph는 SajuIcons.tsx에 없는 고유 도형만 보유한다(중복 진실원천 제거).
|
||||||
|
type GlyphName = 'sun' | 'good' | 'neutral' | 'caution' | 'social';
|
||||||
|
type ReusedIconName = 'great' | 'money' | 'love' | 'career' | 'health';
|
||||||
|
type IconName = GlyphName | ReusedIconName;
|
||||||
|
|
||||||
const GLYPH_PATHS: Record<GlyphName, string> = {
|
const GLYPH_PATHS: Record<GlyphName, string> = {
|
||||||
sun: 'M12 8a4 4 0 100 8 4 4 0 000-8zM12 2v2M12 20v2M4 12H2M22 12h-2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41',
|
sun: 'M12 8a4 4 0 100 8 4 4 0 000-8zM12 2v2M12 20v2M4 12H2M22 12h-2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41',
|
||||||
great: 'M12 3l2.2 5.6L20 9l-4.5 3.9L17 19l-5-3-5 3 1.5-6.1L4 9l5.8-.4z',
|
|
||||||
good: 'M4 12l5 5L20 6',
|
good: 'M4 12l5 5L20 6',
|
||||||
neutral: 'M4 13c2-2 4-2 6 0s4 2 6 0 4-2 6 0',
|
neutral: 'M4 13c2-2 4-2 6 0s4 2 6 0 4-2 6 0',
|
||||||
caution: 'M12 3l9 16H3zM12 10v4M12 16.5h.01',
|
caution: 'M12 3l9 16H3zM12 10v4M12 16.5h.01',
|
||||||
money: 'M12 4a8 4 0 1 0 0 8a8 4 0 1 0 0-8M4 8v8a8 4 0 0 0 16 0V8',
|
|
||||||
love: 'M12 20s-7-4.4-7-9a4 4 0 0 1 7-2.6A4 4 0 0 1 19 11c0 4.6-7 9-7 9z',
|
|
||||||
career: 'M4 8h16v11H4zM9 8V5h6v3',
|
|
||||||
health: 'M3 12h4l2-5 3 10 2-6 2 1h5',
|
|
||||||
social: 'M9 11a3 3 0 100-6 3 3 0 000 6zM3 20c0-3 3-5 6-5s6 2 6 5M17 11a3 3 0 100-6M15 20c0-2.5 1.5-4.5 4-5',
|
social: 'M9 11a3 3 0 100-6 3 3 0 000 6zM3 20c0-3 3-5 6-5s6 2 6 5M17 11a3 3 0 100-6M15 20c0-2.5 1.5-4.5 4-5',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SajuIcons.tsx의 기존 도형과 겹치는 항목 → SajuIcon name 매핑(재사용)
|
||||||
|
const REUSED_ICON: Record<ReusedIconName, SajuIconName> = {
|
||||||
|
great: 'trait', money: 'wealth', love: 'love', career: 'career', health: 'health',
|
||||||
|
};
|
||||||
|
|
||||||
|
function isReusedIcon(name: IconName): name is ReusedIconName {
|
||||||
|
return name in REUSED_ICON;
|
||||||
|
}
|
||||||
|
|
||||||
function Glyph({ name, className }: { name: GlyphName; className?: string }) {
|
function Glyph({ name, className }: { name: GlyphName; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}
|
||||||
@@ -99,8 +108,14 @@ function Glyph({ name, className }: { name: GlyphName; className?: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 로컬 Glyph / SajuIcons 재사용 아이콘을 함께 렌더링하는 디스패처(시각 결과 동일)
|
||||||
|
function Icon({ name, className }: { name: IconName; className?: string }) {
|
||||||
|
if (isReusedIcon(name)) return <SajuIcon name={REUSED_ICON[name]} className={className} />;
|
||||||
|
return <Glyph name={name} className={className} />;
|
||||||
|
}
|
||||||
|
|
||||||
// ── 운세 항목 빌드 ────────────────────────────────────────────────────
|
// ── 운세 항목 빌드 ────────────────────────────────────────────────────
|
||||||
type Area = { icon: GlyphName; label: string; score: number; desc: string };
|
type Area = { icon: IconName; label: string; score: number; desc: string };
|
||||||
|
|
||||||
const DESCS: Record<string, Record<Level, string>> = {
|
const DESCS: Record<string, Record<Level, string>> = {
|
||||||
money: {
|
money: {
|
||||||
@@ -145,7 +160,7 @@ function buildAreas(
|
|||||||
const rand = seededRand(seed);
|
const rand = seededRand(seed);
|
||||||
const roll = () => Math.round(Math.max(15, Math.min(98, rand() * 40 + overall - 20)));
|
const roll = () => Math.round(Math.max(15, Math.min(98, rand() * 40 + overall - 20)));
|
||||||
const keys = ['money','love','career','health','social'] as const;
|
const keys = ['money','love','career','health','social'] as const;
|
||||||
const icons: GlyphName[] = ['money','love','career','health','social'];
|
const icons: IconName[] = ['money','love','career','health','social'];
|
||||||
const labels = ['재물운','애정운','직업운','건강운','사회운'];
|
const labels = ['재물운','애정운','직업운','건강운','사회운'];
|
||||||
return keys.map((k, i) => {
|
return keys.map((k, i) => {
|
||||||
const s = roll();
|
const s = roll();
|
||||||
@@ -154,7 +169,7 @@ function buildAreas(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── 레벨별 색상/라벨 ─────────────────────────────────────────────────
|
// ── 레벨별 색상/라벨 ─────────────────────────────────────────────────
|
||||||
const LEVEL_META: Record<Level, { icon: GlyphName; label: string; bar: string; bg: string; border: string; text: string; badge: string }> = {
|
const LEVEL_META: Record<Level, { icon: IconName; label: string; bar: string; bg: string; border: string; text: string; badge: string }> = {
|
||||||
great: { icon:'great', label:'아주 좋은 날', bar:'#f59e0b', bg:'bg-amber-50', border:'border-amber-300', text:'text-amber-800', badge:'bg-amber-100 text-amber-700 border-amber-300' },
|
great: { icon:'great', label:'아주 좋은 날', bar:'#f59e0b', bg:'bg-amber-50', border:'border-amber-300', text:'text-amber-800', badge:'bg-amber-100 text-amber-700 border-amber-300' },
|
||||||
good: { icon:'good', label:'좋은 날', bar:'#22c55e', bg:'bg-emerald-50',border:'border-emerald-300',text:'text-emerald-800',badge:'bg-emerald-100 text-emerald-700 border-emerald-300' },
|
good: { icon:'good', label:'좋은 날', bar:'#22c55e', bg:'bg-emerald-50',border:'border-emerald-300',text:'text-emerald-800',badge:'bg-emerald-100 text-emerald-700 border-emerald-300' },
|
||||||
neutral: { icon:'neutral', label:'평온한 날', bar:'#64748b', bg:'bg-slate-50', border:'border-slate-200', text:'text-slate-700', badge:'bg-slate-100 text-slate-600 border-slate-200' },
|
neutral: { icon:'neutral', label:'평온한 날', bar:'#64748b', bg:'bg-slate-50', border:'border-slate-200', text:'text-slate-700', badge:'bg-slate-100 text-slate-600 border-slate-200' },
|
||||||
@@ -235,7 +250,7 @@ export default function SajuFortuneSection({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className={`text-[11px] font-extrabold px-3 py-1.5 rounded-full border ${meta.badge} flex-shrink-0 flex items-center gap-1`}>
|
<span className={`text-[11px] font-extrabold px-3 py-1.5 rounded-full border ${meta.badge} flex-shrink-0 flex items-center gap-1`}>
|
||||||
<Glyph name={meta.icon} className="w-3 h-3" /> {meta.label}
|
<Icon name={meta.icon} className="w-3 h-3" /> {meta.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,13 +296,13 @@ export default function SajuFortuneSection({
|
|||||||
return (
|
return (
|
||||||
<div key={area.label} className="flex gap-3 items-start">
|
<div key={area.label} className="flex gap-3 items-start">
|
||||||
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${aMeta.bg} border ${aMeta.border} ${aMeta.text}`}>
|
<div className={`w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0 ${aMeta.bg} border ${aMeta.border} ${aMeta.text}`}>
|
||||||
<Glyph name={area.icon} className="w-4 h-4" />
|
<Icon name={area.icon} className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-between mb-0.5">
|
<div className="flex items-center justify-between mb-0.5">
|
||||||
<span className="text-xs font-bold text-[var(--jsm-ink)]">{area.label}</span>
|
<span className="text-xs font-bold text-[var(--jsm-ink)]">{area.label}</span>
|
||||||
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded-full border ${aMeta.badge} flex items-center gap-1`}>
|
<span className={`text-[10px] font-bold px-1.5 py-0.5 rounded-full border ${aMeta.badge} flex items-center gap-1`}>
|
||||||
<Glyph name={aMeta.icon} className="w-2.5 h-2.5" />
|
<Icon name={aMeta.icon} className="w-2.5 h-2.5" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<ScoreBar score={area.score} color={aMeta.bar} />
|
<ScoreBar score={area.score} color={aMeta.bar} />
|
||||||
|
|||||||
Reference in New Issue
Block a user