Files
jaengseung-made/app/services/lotto/recommend/page.tsx

1129 lines
75 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect, useRef } from 'react';
import Link from 'next/link';
import { createClient } from '@/lib/supabase/client';
// ─── 전략 타입 ────────────────────────────────────────────────────────────────
type Strategy = 'balanced' | 'aggressive' | 'safe';
const STRATEGY_INFO: Record<Strategy, { label: string; desc: string; icon: string; color: string; accentColor: string }> = {
balanced: { label: '균형형', desc: '합계·홀짝·구간 고르게 최적화', icon: '⚖️', color: 'rgba(251,191,36,.12)', accentColor: '#fbbf24' },
aggressive: { label: '고위험형', desc: '미출현 냉각 번호 중점 포함', icon: '🔥', color: 'rgba(239,68,68,.1)', accentColor: '#f87171' },
safe: { label: '안정형', desc: '과출현 핫 번호 + 균등 분산', icon: '🛡️', color: 'rgba(96,165,250,.1)', accentColor: '#60a5fa' },
};
// ─── 클라이언트 Monte Carlo 폴백 ─────────────────────────────────────────────
function clientMonteCarlo(strategy: Strategy = 'balanced'): { numbers: number[]; metrics: { sum: number; odd: number; even: number; min: number; max: number; range: number } } {
const SIMS = 5000;
let best: number[] = [];
let bestScore = -Infinity;
for (let i = 0; i < SIMS; i++) {
const nums = pickRandom6();
const score = scoreCombo(nums, strategy);
if (score > bestScore) { bestScore = score; best = nums; }
}
const sorted = [...best].sort((a, b) => a - b);
const sum = sorted.reduce((a, b) => a + b, 0);
const odd = sorted.filter(n => n % 2 !== 0).length;
return {
numbers: sorted,
metrics: { sum, odd, even: 6 - odd, min: sorted[0], max: sorted[5], range: sorted[5] - sorted[0] },
};
}
function pickRandom6(): number[] {
const pool = Array.from({ length: 45 }, (_, i) => i + 1);
const result: number[] = [];
while (result.length < 6) {
const idx = Math.floor(Math.random() * pool.length);
result.push(pool.splice(idx, 1)[0]);
}
return result;
}
function scoreCombo(nums: number[], strategy: Strategy = 'balanced'): number {
const sorted = [...nums].sort((a, b) => a - b);
const sum = sorted.reduce((a, b) => a + b, 0);
const odd = sorted.filter(n => n % 2 !== 0).length;
const zones = new Set(sorted.map(n => Math.min(Math.floor((n - 1) / 10), 4)));
if (strategy === 'aggressive') {
// 고위험형: 높은 번호·냉각 구간 선호, 합계 편차 허용
const sumScore = -Math.abs(sum - 158) / 55;
const oddScore = odd >= 2 && odd <= 4 ? 0.3 : -0.3;
const zoneScore = zones.size * 0.2;
const highNums = sorted.filter(n => n > 35).length;
return sumScore + oddScore + zoneScore + highNums * 0.18 + Math.random() * 0.05;
} else if (strategy === 'safe') {
// 안정형: 최적 합계 엄격하게, 구간 분산 최대화
const sumScore = -Math.abs(sum - 138) / 22;
const oddScore = odd === 3 ? 0.9 : odd === 2 || odd === 4 ? 0.35 : -0.6;
const zoneScore = zones.size * 0.65;
return sumScore + oddScore + zoneScore + Math.random() * 0.04;
} else {
// 균형형 (기본)
const sumScore = -Math.abs(sum - 138) / 35;
const oddScore = odd >= 2 && odd <= 4 ? 0.5 : -0.5;
const zoneScore = zones.size * 0.4;
return sumScore + oddScore + zoneScore + Math.random() * 0.05;
}
}
// ─── Types ───────────────────────────────────────────────────────────────────
interface LottoMetrics {
sum: number;
odd: number;
even: number;
min: number;
max: number;
range: number;
}
interface RecommendResponse {
ok: boolean;
plan: string;
numbers: number[];
metrics?: LottoMetrics;
recent_overlap?: { repeated_numbers: number[] };
}
interface BatchResponse {
ok: boolean;
plan: string;
count: number;
items: Array<{ numbers: number[]; metrics?: LottoMetrics }>;
}
interface NumberStat {
number: number;
frequency_pct: number;
z_score: number;
gap: number;
}
interface DashboardResponse {
ok: boolean;
plan: string;
latest: {
drawNo: number;
date: string;
numbers: number[];
bonus: number;
metrics: LottoMetrics;
} | null;
analysis: {
total_draws: number;
mean_sum: number;
number_stats: NumberStat[];
} | null;
simulation: {
runs: Array<{
id: number;
run_at: string;
strategy: string;
total_generated: number;
avg_score: number;
}>;
} | null;
}
interface Combo {
id: number;
numbers: number[];
metrics?: LottoMetrics;
overlap?: number[];
createdAt: Date;
}
type GenMode = 'single' | 'batch';
const PLAN_LABELS: Record<string, string> = {
lotto_gold: '🥇 골드',
lotto_platinum: '💎 플래티넘',
lotto_diamond: '👑 다이아',
};
const PLAN_MAX_COMBOS: Record<string, number> = {
lotto_gold: 1,
lotto_platinum: 3,
lotto_diamond: 999,
};
// ─── Lotto Ball ───────────────────────────────────────────────────────────────
function getBallStyle(n: number): { bg: string; shadow: string; text: string } {
if (n <= 10) return { bg: 'linear-gradient(145deg,#fde68a,#fbbf24,#d97706)', shadow: 'rgba(251,191,36,.6)', text: '#78350f' };
if (n <= 20) return { bg: 'linear-gradient(145deg,#93c5fd,#3b82f6,#1d4ed8)', shadow: 'rgba(59,130,246,.6)', text: '#fff' };
if (n <= 30) return { bg: 'linear-gradient(145deg,#fca5a5,#ef4444,#b91c1c)', shadow: 'rgba(239,68,68,.6)', text: '#fff' };
if (n <= 40) return { bg: 'linear-gradient(145deg,#d1d5db,#9ca3af,#4b5563)', shadow: 'rgba(107,114,128,.6)', text: '#fff' };
return { bg: 'linear-gradient(145deg,#86efac,#22c55e,#15803d)', shadow: 'rgba(34,197,94,.6)', text: '#fff' };
}
function LottoBall({ n, size = 52, delay = 0, bounce = false, highlight = false }: {
n: number; size?: number; delay?: number; bounce?: boolean; highlight?: boolean;
}) {
const [show, setShow] = useState(!bounce);
const { bg, shadow, text } = getBallStyle(n);
useEffect(() => {
if (!bounce) return;
const t = setTimeout(() => setShow(true), delay);
return () => clearTimeout(t);
}, [bounce, delay]);
return (
<div style={{
width: size, height: size, borderRadius: '50%', background: bg,
boxShadow: `0 ${size * .08}px ${size * .3}px ${shadow}${highlight ? ',0 0 0 3px rgba(251,191,36,.8),0 0 20px rgba(251,191,36,.4)' : ''},inset 0 1px 0 rgba(255,255,255,.45),inset 0 -2px 4px rgba(0,0,0,.18)`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: size * .35, fontWeight: 900, color: text, flexShrink: 0,
position: 'relative', userSelect: 'none',
opacity: show ? 1 : 0,
transform: show ? 'scale(1) translateY(0)' : 'scale(.2) translateY(20px)',
transition: `opacity .35s ease ${delay}ms,transform .55s cubic-bezier(.34,1.56,.64,1) ${delay}ms`,
}}>
<div style={{ position: 'absolute', top: '14%', left: '18%', width: '38%', height: '28%', background: 'rgba(255,255,255,.38)', borderRadius: '50%', filter: 'blur(2px)', transform: 'rotate(-30deg)', pointerEvents: 'none' }} />
<span style={{ position: 'relative', zIndex: 1 }}>{n}</span>
</div>
);
}
function SpinBall({ n, delay = 0 }: { n: number; delay?: number }) {
const { bg, shadow, text } = getBallStyle(n);
return (
<div style={{
width: 52, height: 52, borderRadius: '50%', background: bg,
boxShadow: `0 4px 16px ${shadow},inset 0 1px 0 rgba(255,255,255,.4)`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 18, fontWeight: 900, color: text, flexShrink: 0,
animation: 'spinBounce .9s ease-in-out infinite', animationDelay: `${delay}ms`,
}}>
{n}
</div>
);
}
// ─── Frequency Bar (z_score 기반) ─────────────────────────────────────────────
function FreqBar({ number, zScore, gap, isHot }: { number: number; zScore: number; gap: number; isHot: boolean }) {
const { bg, shadow } = getBallStyle(number);
const barWidth = Math.min(100, Math.abs(zScore) * 40 + 20);
const barColor = isHot
? 'linear-gradient(90deg,rgba(239,68,68,.8),rgba(251,113,133,.6))'
: 'linear-gradient(90deg,rgba(96,165,250,.8),rgba(147,197,253,.6))';
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '.65rem', padding: '.3rem 0' }}>
<div style={{
width: 30, height: 30, borderRadius: '50%', background: bg,
boxShadow: `0 2px 8px ${shadow}`, display: 'flex', alignItems: 'center',
justifyContent: 'center', fontSize: '.72rem', fontWeight: 900,
color: number <= 10 ? '#78350f' : '#fff', flexShrink: 0,
}}>
{number}
</div>
<div style={{ flex: 1, position: 'relative' }}>
<div style={{ height: 6, background: 'rgba(255,255,255,.05)', borderRadius: 3, overflow: 'hidden' }}>
<div style={{
height: '100%', width: `${barWidth}%`, background: barColor,
borderRadius: 3, transition: 'width 1s ease',
}} />
</div>
</div>
<div style={{ textAlign: 'right', minWidth: 48 }}>
<div style={{ color: isHot ? '#fca5a5' : '#93c5fd', fontSize: '.65rem', fontWeight: 700 }}>
{isHot ? `+${zScore.toFixed(2)}σ` : `${gap}회 미출`}
</div>
</div>
</div>
);
}
// ─── Odd/Even Mini Bar ─────────────────────────────────────────────────────────
function OddEvenBar({ odd, even }: { odd: number; even: number }) {
const total = odd + even;
const oddPct = total > 0 ? (odd / total) * 100 : 50;
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '.4rem' }}>
<span style={{ color: 'rgba(251,191,36,.4)', fontSize: '.6rem', minWidth: 20 }}>{odd}</span>
<div style={{ flex: 1, height: 5, background: 'rgba(255,255,255,.06)', borderRadius: 3, overflow: 'hidden', position: 'relative' }}>
<div style={{ position: 'absolute', left: 0, top: 0, height: '100%', width: `${oddPct}%`, background: 'linear-gradient(90deg,#f59e0b,#fbbf24)', borderRadius: '3px 0 0 3px', transition: 'width .6s ease' }} />
<div style={{ position: 'absolute', right: 0, top: 0, height: '100%', width: `${100 - oddPct}%`, background: 'linear-gradient(90deg,#60a5fa,#93c5fd)', borderRadius: '0 3px 3px 0', transition: 'width .6s ease' }} />
</div>
<span style={{ color: 'rgba(96,165,250,.4)', fontSize: '.6rem', minWidth: 20, textAlign: 'right' }}>{even}</span>
</div>
);
}
// ─── Animated Counter ─────────────────────────────────────────────────────────
function AnimCounter({ value, duration = 1200 }: { value: number; duration?: number }) {
const [display, setDisplay] = useState(0);
const raf = useRef<number | null>(null);
useEffect(() => {
const start = performance.now();
const animate = (now: number) => {
const progress = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setDisplay(Math.floor(eased * value));
if (progress < 1) raf.current = requestAnimationFrame(animate);
};
raf.current = requestAnimationFrame(animate);
return () => { if (raf.current) cancelAnimationFrame(raf.current); };
}, [value, duration]);
return <>{display.toLocaleString()}</>;
}
// ─── Main Page ────────────────────────────────────────────────────────────────
export default function LottoRecommendPage() {
const supabase = createClient();
const [isSubscribed, setIsSubscribed] = useState(false);
const [plan, setPlan] = useState('');
const [dashboard, setDashboard] = useState<DashboardResponse | null>(null);
const [pageReady, setPageReady] = useState(false);
const [previewNumbers, setPreviewNumbers] = useState<number[]>([]);
const [previewMetrics, setPreviewMetrics] = useState<LottoMetrics | null>(null);
const [previewState, setPreviewState] = useState<'idle' | 'loading' | 'result' | 'error'>('idle');
const [previewUsed, setPreviewUsed] = useState(false);
const [previewSource, setPreviewSource] = useState<'nas' | 'client'>('client');
const [genMode, setGenMode] = useState<GenMode>('single');
const [strategy, setStrategy] = useState<Strategy>('balanced');
const [combos, setCombos] = useState<Combo[]>([]);
const [proState, setProState] = useState<'idle' | 'loading' | 'result' | 'error'>('idle');
const [proError, setProError] = useState('');
const idRef = useRef(0);
const MAX_COMBOS = PLAN_MAX_COMBOS[plan] ?? 5;
const SPIN_NUMS = [7, 23, 41, 14, 35, 3];
useEffect(() => {
async function init() {
const { data: { user } } = await supabase.auth.getUser();
if (user) {
try {
const res = await fetch('/api/lotto/dashboard');
if (res.ok) {
const data: DashboardResponse = await res.json();
setDashboard(data);
setPlan(data.plan ?? '');
setIsSubscribed(true);
}
} catch { /* ignore */ }
}
setPageReady(true);
}
init();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handlePreview = async () => {
if (previewState === 'loading') return;
setPreviewState('loading');
try {
const res = await fetch('/api/lotto/preview');
if (res.ok) {
const data = await res.json();
setPreviewNumbers([...data.numbers].sort((a, b) => a - b));
setPreviewMetrics(data.metrics ?? null);
setPreviewSource('nas');
} else {
const { numbers, metrics } = clientMonteCarlo();
setPreviewNumbers(numbers);
setPreviewMetrics(metrics);
setPreviewSource('client');
}
setPreviewState('result');
setPreviewUsed(true);
} catch {
try {
const { numbers, metrics } = clientMonteCarlo();
setPreviewNumbers(numbers);
setPreviewMetrics(metrics);
setPreviewSource('client');
setPreviewState('result');
setPreviewUsed(true);
} catch {
setPreviewState('error');
}
}
};
const saveHistory = (numbers: number[], source: 'nas' | 'client') => {
if (!plan) return;
fetch('/api/lotto/history', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ numbers, source, plan_id: plan }),
}).catch(() => { });
};
const handleGenerate = async () => {
if (proState === 'loading' || combos.length >= MAX_COMBOS) return;
setProState('loading');
setProError('');
try {
const url = genMode === 'batch'
? '/api/lotto/recommend?mode=batch'
: '/api/lotto/recommend?mode=single';
const res = await fetch(url);
if (res.status === 403) { setIsSubscribed(false); setProState('idle'); return; }
if (res.status === 503) {
const count = genMode === 'batch' ? Math.min(5, MAX_COMBOS - combos.length) : 1;
const newCombos: Combo[] = Array.from({ length: count }, () => {
idRef.current += 1;
const { numbers, metrics } = clientMonteCarlo(strategy);
saveHistory(numbers, 'client');
return { id: idRef.current, numbers, metrics, createdAt: new Date() };
});
setCombos((prev) => [...prev, ...newCombos].slice(-MAX_COMBOS));
setProState('result');
return;
}
if (!res.ok) { const e = await res.json(); throw new Error(e.error ?? 'API_ERROR'); }
if (genMode === 'batch') {
const data: BatchResponse = await res.json();
const newCombos: Combo[] = (data.items ?? []).map((item) => {
idRef.current += 1;
const numbers = [...item.numbers].sort((a, b) => a - b);
saveHistory(numbers, 'nas');
return { id: idRef.current, numbers, metrics: item.metrics, createdAt: new Date() };
});
setCombos((prev) => [...prev, ...newCombos].slice(-MAX_COMBOS));
} else {
const data: RecommendResponse = await res.json();
if (!data.numbers?.length) throw new Error('EMPTY_RESULT');
idRef.current += 1;
const numbers = [...data.numbers].sort((a, b) => a - b);
saveHistory(numbers, 'nas');
setCombos((prev) => [...prev, {
id: idRef.current,
numbers,
metrics: data.metrics,
overlap: data.recent_overlap?.repeated_numbers,
createdAt: new Date(),
}]);
}
setProState('result');
} catch (err: unknown) {
const e = err as { message?: string };
setProError(e?.message === 'NAS_TIMEOUT' ? 'NAS 서버 응답 시간 초과.' : '생성 중 오류가 발생했습니다.');
setProState('error');
}
};
const clearCombos = () => { setCombos([]); setProState('idle'); setProError(''); };
// 핫/콜드 (z_score 포함)
const numberStats = dashboard?.analysis?.number_stats ?? [];
const hotStats = numberStats
.filter(s => s.z_score > 0.3)
.sort((a, b) => b.z_score - a.z_score)
.slice(0, 8);
const coldStats = numberStats
.filter(s => s.z_score < -0.3)
.sort((a, b) => b.gap - a.gap)
.slice(0, 8);
const hotNumbers = hotStats.map(s => s.number);
const coldNumbers = coldStats.map(s => s.number);
const latestRun = dashboard?.simulation?.runs?.[0];
const totalDraws = dashboard?.analysis?.total_draws;
const isProLoading = proState === 'loading';
const isMaxed = combos.length >= MAX_COMBOS;
const latestCombo = combos.length > 0 ? combos[combos.length - 1] : null;
const simTotal = latestRun?.total_generated ?? 100000;
const drawsCount = totalDraws ?? 1130;
if (!pageReady) {
return (
<div style={{ minHeight: '100vh', background: 'linear-gradient(160deg,#04102b,#020b1a)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ textAlign: 'center' }}>
<div style={{ width: 44, height: 44, borderRadius: '50%', border: '3px solid rgba(251,191,36,.15)', borderTop: '3px solid #fbbf24', animation: 'spin .8s linear infinite', margin: '0 auto 1rem' }} />
<div style={{ color: 'rgba(251,191,36,.4)', fontSize: '.75rem', letterSpacing: '.12em' }}>INITIALIZING</div>
</div>
</div>
);
}
return (
<>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Black+Han+Sans&family=JetBrains+Mono:wght@400;600;700&family=Noto+Sans+KR:wght@400;600;700;800;900&display=swap');
@keyframes spinBounce { 0%,100%{transform:translateY(0) rotate(0deg)} 25%{transform:translateY(-14px) rotate(90deg)} 50%{transform:translateY(0) rotate(180deg)} 75%{transform:translateY(-7px) rotate(270deg)} }
@keyframes spin { to{transform:rotate(360deg)} }
@keyframes shimmer { 0%{background-position:-200% center} 100%{background-position:200% center} }
@keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-9px)} }
@keyframes glowPulse { 0%,100%{opacity:.7} 50%{opacity:1} }
@keyframes slideUp { from{opacity:0;transform:translateY(18px)} to{opacity:1;transform:translateY(0)} }
@keyframes slideIn { from{opacity:0;transform:translateX(-12px)} to{opacity:1;transform:translateX(0)} }
@keyframes orbFloat1 { 0%,100%{transform:translate(0,0) scale(1)} 33%{transform:translate(30px,-20px) scale(1.08)} 66%{transform:translate(-20px,30px) scale(.92)} }
@keyframes orbFloat2 { 0%,100%{transform:translate(0,0) scale(1)} 33%{transform:translate(-30px,20px) scale(.92)} 66%{transform:translate(20px,-30px) scale(1.08)} }
@keyframes badgePop { 0%{transform:scale(.7);opacity:0} 70%{transform:scale(1.08)} 100%{transform:scale(1);opacity:1} }
@keyframes scanLine { 0%{transform:translateY(-100%)} 100%{transform:translateY(100vh)} }
@keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.04)} }
@keyframes dataFlow { 0%{opacity:.2;transform:translateY(0)} 50%{opacity:.6} 100%{opacity:.2;transform:translateY(-8px)} }
@keyframes borderGlow { 0%,100%{border-color:rgba(251,191,36,.2)} 50%{border-color:rgba(251,191,36,.55)} }
.gen-btn:not(:disabled):hover{transform:translateY(-2px) scale(1.02)!important;box-shadow:0 12px 48px rgba(251,191,36,.5)!important}
.gen-btn:not(:disabled):active{transform:translateY(0) scale(.97)!important}
.combo-card{animation:slideUp .4s ease forwards;opacity:0}
.mode-tab{transition:all .2s ease}
.preview-btn:not(:disabled):hover{opacity:.9;transform:translateY(-2px)!important;box-shadow:0 8px 32px rgba(34,197,94,.4)!important}
.hot-row{animation:slideIn .4s ease forwards;opacity:0}
.stat-panel:hover{border-color:rgba(251,191,36,.3)!important;transform:translateY(-1px)}
.stat-panel{transition:all .2s ease}
`}</style>
<div style={{ minHeight: '100%', background: 'linear-gradient(160deg,#020c1e 0%,#04102b 30%,#0a0500 65%,#020b1a 100%)', fontFamily: "'Noto Sans KR',sans-serif", position: 'relative', overflow: 'hidden' }}>
{/* Ambient orbs */}
<div style={{ position: 'fixed', top: '5%', left: '2%', width: 500, height: 500, borderRadius: '50%', background: 'radial-gradient(circle,rgba(251,191,36,.05),transparent 70%)', animation: 'orbFloat1 16s ease-in-out infinite', pointerEvents: 'none', zIndex: 0 }} />
<div style={{ position: 'fixed', bottom: '5%', right: '2%', width: 600, height: 600, borderRadius: '50%', background: 'radial-gradient(circle,rgba(6,182,212,.04),transparent 70%)', animation: 'orbFloat2 20s ease-in-out infinite', pointerEvents: 'none', zIndex: 0 }} />
<div style={{ position: 'fixed', top: '40%', right: '15%', width: 300, height: 300, borderRadius: '50%', background: 'radial-gradient(circle,rgba(251,191,36,.03),transparent 70%)', animation: 'orbFloat1 25s ease-in-out infinite', pointerEvents: 'none', zIndex: 0 }} />
<div style={{ position: 'relative', zIndex: 1, maxWidth: 960, margin: '0 auto', padding: '2rem 1.25rem 4rem' }}>
{/* ── Navigation ── */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '2rem' }}>
<Link href="/services/lotto" style={{ display: 'inline-flex', alignItems: 'center', gap: '.4rem', color: 'rgba(6,182,212,.5)', fontSize: '.72rem', textDecoration: 'none', fontFamily: "'JetBrains Mono',monospace", letterSpacing: '.08em', transition: 'color .2s' }}>
<svg width={12} height={12} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}><path d="M19 12H5M12 5l-7 7 7 7" /></svg>
LOTTO SERVICE
</Link>
{isSubscribed && plan && (
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '.5rem', background: 'linear-gradient(135deg,rgba(251,191,36,.1),rgba(249,115,22,.06))', border: '1px solid rgba(251,191,36,.25)', borderRadius: '2rem', padding: '.4rem 1rem', animation: 'borderGlow 3s ease-in-out infinite' }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#4ade80', boxShadow: '0 0 8px rgba(74,222,128,.8)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ color: '#fbbf24', fontSize: '.75rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}>{PLAN_LABELS[plan] ?? plan}</span>
</div>
)}
</div>
{/* ── Header ── */}
<div style={{ marginBottom: '2.5rem' }}>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.65rem', fontWeight: 700, letterSpacing: '.2em', textTransform: 'uppercase', color: 'rgba(6,182,212,.6)', marginBottom: '.75rem', display: 'flex', alignItems: 'center', gap: '.5rem' }}>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: '#06b6d4', animation: 'glowPulse 2s ease-in-out infinite' }} />
MONTE CARLO SIMULATION ENGINE
</div>
<h1 style={{ fontFamily: "'Black Han Sans',sans-serif", fontSize: 'clamp(2rem,5vw,3rem)', color: '#fff', margin: '0 0 .5rem', lineHeight: 1.05, letterSpacing: '-.02em' }}>
<br />
<span style={{ background: 'linear-gradient(90deg,#fbbf24,#f97316,#fb923c,#fbbf24)', backgroundSize: '200% auto', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', animation: 'shimmer 3s linear infinite' }}> </span>
</h1>
<p style={{ color: 'rgba(255,255,255,.3)', fontSize: '.82rem', margin: 0, lineHeight: 1.6 }}>
{drawsCount.toLocaleString()} · 5,000 Monte Carlo
</p>
</div>
{/* ── 통계 인디케이터 패널 (전체 공개) ── */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: '.75rem', marginBottom: '2rem' }}>
{[
{ label: 'SIMULATION', value: simTotal, suffix: '회', color: '#fbbf24', icon: '⚡', desc: '시뮬레이션 횟수' },
{ label: 'DRAWS ANALYZED', value: drawsCount, suffix: '회', color: '#06b6d4', icon: '📊', desc: '분석 회차' },
{ label: 'HOT NUMBERS', value: hotNumbers.length, suffix: '개', color: '#f87171', icon: '🔥', desc: '과출현 번호' },
{ label: 'COLD NUMBERS', value: coldNumbers.length, suffix: '개', color: '#60a5fa', icon: '❄️', desc: '미출현 번호' },
].map((s, i) => (
<div key={s.label} className="stat-panel" style={{ background: 'linear-gradient(145deg,rgba(255,255,255,.03),rgba(255,255,255,.01))', border: '1px solid rgba(255,255,255,.06)', borderRadius: '1rem', padding: '1rem', position: 'relative', overflow: 'hidden', animationDelay: `${i * 80}ms` }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: `linear-gradient(90deg,transparent,${s.color}40,transparent)` }} />
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.55rem', letterSpacing: '.15em', color: 'rgba(255,255,255,.2)', marginBottom: '.5rem' }}>{s.label}</div>
<div style={{ color: s.color, fontSize: 'clamp(.9rem,2vw,1.3rem)', fontWeight: 900, fontFamily: "'JetBrains Mono',monospace", lineHeight: 1 }}>
<AnimCounter value={s.value} />
<span style={{ fontSize: '.65em', opacity: .7 }}>{s.suffix}</span>
</div>
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.6rem', marginTop: '.3rem' }}>{s.desc}</div>
</div>
))}
</div>
{/* ── 이번 주 공략 포인트 (구독자 전용) ── */}
{isSubscribed && dashboard?.analysis && (
<div style={{ background: 'linear-gradient(145deg,rgba(6,182,212,.06),rgba(99,102,241,.03))', border: '1px solid rgba(6,182,212,.15)', borderRadius: '1.25rem', padding: '1.25rem 1.5rem', marginBottom: '1.5rem', animation: 'slideUp .5s ease' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem', marginBottom: '1rem' }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#06b6d4', boxShadow: '0 0 8px rgba(6,182,212,.7)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.6rem', letterSpacing: '.15em', color: 'rgba(6,182,212,.7)', textTransform: 'uppercase' }}>
WEEKLY ATTACK REPORT ·
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(200px,1fr))', gap: '.75rem' }}>
{/* 핫 넘버 공략 */}
{hotStats.length > 0 && (
<div style={{ background: 'rgba(0,0,0,.25)', borderRadius: '.875rem', padding: '.875rem 1rem', borderLeft: '2px solid rgba(239,68,68,.5)' }}>
<div style={{ display: 'flex', gap: '.3rem', marginBottom: '.5rem', flexWrap: 'wrap' }}>
{hotStats.slice(0, 5).map(s => {
const { bg } = getBallStyle(s.number);
return (
<div key={s.number} style={{ width: 24, height: 24, borderRadius: '50%', background: bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '.6rem', fontWeight: 900, color: s.number <= 10 ? '#78350f' : '#fff' }}>
{s.number}
</div>
);
})}
</div>
<div style={{ color: '#fca5a5', fontSize: '.7rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}>🔥 </div>
<div style={{ color: 'rgba(255,255,255,.25)', fontSize: '.62rem', marginTop: '.2rem', lineHeight: 1.5 }}>
·
</div>
</div>
)}
{/* 콜드 넘버 공략 */}
{coldStats.length > 0 && (
<div style={{ background: 'rgba(0,0,0,.25)', borderRadius: '.875rem', padding: '.875rem 1rem', borderLeft: '2px solid rgba(96,165,250,.5)' }}>
<div style={{ display: 'flex', gap: '.3rem', marginBottom: '.5rem', flexWrap: 'wrap' }}>
{coldStats.slice(0, 5).map(s => {
const { bg } = getBallStyle(s.number);
return (
<div key={s.number} style={{ width: 24, height: 24, borderRadius: '50%', background: bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '.6rem', fontWeight: 900, color: s.number <= 10 ? '#78350f' : '#fff' }}>
{s.number}
</div>
);
})}
</div>
<div style={{ color: '#93c5fd', fontSize: '.7rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}> </div>
<div style={{ color: 'rgba(255,255,255,.25)', fontSize: '.62rem', marginTop: '.2rem', lineHeight: 1.5 }}>
{Math.round(coldStats.reduce((a, s) => a + s.gap, 0) / coldStats.length)} ·
</div>
</div>
)}
{/* AI 신뢰도 */}
{dashboard.latest && (
<div style={{ background: 'rgba(0,0,0,.25)', borderRadius: '.875rem', padding: '.875rem 1rem', borderLeft: '2px solid rgba(251,191,36,.4)' }}>
<div style={{ marginBottom: '.5rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '.25rem' }}>
<span style={{ color: 'rgba(251,191,36,.6)', fontSize: '.6rem', fontFamily: "'JetBrains Mono',monospace" }}>AI CONFIDENCE</span>
<span style={{ color: '#fbbf24', fontSize: '.7rem', fontWeight: 800, fontFamily: "'JetBrains Mono',monospace" }}>
{Math.min(99, 60 + hotStats.length * 2 + coldStats.length)}%
</span>
</div>
<div style={{ height: 5, background: 'rgba(255,255,255,.05)', borderRadius: 3, overflow: 'hidden' }}>
<div style={{ height: '100%', width: `${Math.min(99, 60 + hotStats.length * 2 + coldStats.length)}%`, background: 'linear-gradient(90deg,#fbbf24,#f59e0b)', borderRadius: 3, transition: 'width 1.5s ease' }} />
</div>
</div>
<div style={{ color: '#fbbf24', fontSize: '.7rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}> </div>
<div style={{ color: 'rgba(255,255,255,.25)', fontSize: '.62rem', marginTop: '.2rem', lineHeight: 1.5 }}>
{(dashboard.analysis.total_draws ?? 1130).toLocaleString()} + {(hotStats.length + coldStats.length)}
</div>
</div>
)}
</div>
</div>
)}
{/* ── 최신 당첨번호 ── */}
{isSubscribed && dashboard?.latest && (
<div style={{ background: 'linear-gradient(145deg,rgba(255,255,255,.03),rgba(255,255,255,.015))', border: '1px solid rgba(255,255,255,.07)', borderRadius: '1.25rem', padding: '1.25rem 1.5rem', marginBottom: '1.5rem', animation: 'slideUp .5s ease' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem', marginBottom: '1rem' }}>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.6rem', letterSpacing: '.15em', color: 'rgba(255,255,255,.25)', textTransform: 'uppercase' }}>LATEST DRAW</div>
<div style={{ flex: 1, height: 1, background: 'rgba(255,255,255,.05)' }} />
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.68rem' }}>{dashboard.latest.drawNo} · {dashboard.latest.date}</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '1.25rem', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', gap: '.4rem', alignItems: 'center', flexWrap: 'wrap' }}>
{dashboard.latest.numbers.map((n, i) => <LottoBall key={i} n={n} size={34} />)}
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.85rem', margin: '0 .15rem' }}></div>
<div style={{ position: 'relative', width: 34, height: 34, borderRadius: '50%', border: '2px dashed rgba(251,191,36,.35)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<LottoBall n={dashboard.latest.bonus} size={28} />
</div>
</div>
<div style={{ marginLeft: 'auto', display: 'flex', gap: '1.5rem' }}>
{[
{ l: '합계', v: dashboard.latest.metrics.sum },
{ l: '홀수', v: `${dashboard.latest.metrics.odd}` },
{ l: '짝수', v: `${dashboard.latest.metrics.even}` },
{ l: '범위', v: dashboard.latest.metrics.range },
].map(s => (
<div key={s.l} style={{ textAlign: 'center' }}>
<div style={{ color: '#fbbf24', fontSize: '.9rem', fontWeight: 800, fontFamily: "'JetBrains Mono',monospace" }}>{s.v}</div>
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.6rem', marginTop: '.15rem' }}>{s.l}</div>
</div>
))}
</div>
</div>
</div>
)}
{/* ════════════════════════════════════════════════
무료 맛보기 섹션
════════════════════════════════════════════════ */}
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.75rem', marginBottom: '.875rem' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '.4rem', background: 'rgba(74,222,128,.08)', border: '1px solid rgba(74,222,128,.2)', borderRadius: '2rem', padding: '.3rem .85rem' }}>
<div style={{ width: 5, height: 5, borderRadius: '50%', background: '#4ade80', boxShadow: '0 0 6px rgba(74,222,128,.8)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ color: '#4ade80', fontSize: '.68rem', fontWeight: 700, letterSpacing: '.1em', fontFamily: "'JetBrains Mono',monospace" }}>FREE PREVIEW</span>
</div>
<span style={{ color: 'rgba(255,255,255,.2)', fontSize: '.72rem' }}>1 · Monte Carlo 5,000 </span>
</div>
<div style={{ background: 'linear-gradient(145deg,rgba(74,222,128,.04),rgba(34,197,94,.01))', border: '1px solid rgba(74,222,128,.12)', borderRadius: '1.5rem', padding: '2rem', position: 'relative', overflow: 'hidden' }}>
{/* Decorative grid lines */}
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(74,222,128,.03) 1px,transparent 1px),linear-gradient(90deg,rgba(74,222,128,.03) 1px,transparent 1px)', backgroundSize: '40px 40px', pointerEvents: 'none', borderRadius: '1.5rem' }} />
<div style={{ position: 'relative' }}>
{/* 번호 표시 */}
<div style={{ minHeight: 100, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '1.5rem', gap: '.75rem', flexWrap: 'wrap' }}>
{previewState === 'loading' ? (
SPIN_NUMS.slice(0, 6).map((n, i) => <SpinBall key={i} n={n} delay={i * 100} />)
) : previewState === 'result' && previewNumbers.length > 0 ? (
previewNumbers.map((n, i) => <LottoBall key={i} n={n} size={64} bounce delay={i * 120} />)
) : (
Array.from({ length: 6 }, (_, i) => (
<div key={i} style={{ width: 64, height: 64, borderRadius: '50%', border: '2px dashed rgba(74,222,128,.18)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(74,222,128,.15)', fontSize: '1.3rem', fontWeight: 900, animation: `float ${2 + i * .28}s ease-in-out infinite`, animationDelay: `${i * .18}s`, fontFamily: "'JetBrains Mono',monospace" }}>?</div>
))
)}
</div>
{/* 맛보기 메트릭 */}
{previewState === 'result' && previewMetrics && (
<div style={{ animation: 'slideUp .4s ease' }}>
<div style={{ display: 'flex', gap: '.6rem', justifyContent: 'center', marginBottom: '.75rem', flexWrap: 'wrap' }}>
{[
{ l: 'SUM', v: previewMetrics.sum, good: previewMetrics.sum >= 100 && previewMetrics.sum <= 175 },
{ l: 'ODD', v: `${previewMetrics.odd}` },
{ l: 'EVEN', v: `${previewMetrics.even}` },
{ l: 'RANGE', v: previewMetrics.range },
].map(s => (
<div key={s.l} style={{ background: 'rgba(74,222,128,.06)', border: '1px solid rgba(74,222,128,.14)', borderRadius: '.625rem', padding: '.4rem .85rem', textAlign: 'center' }}>
<div style={{ color: '#4ade80', fontSize: '.85rem', fontWeight: 800, fontFamily: "'JetBrains Mono',monospace" }}>{s.v}</div>
<div style={{ color: 'rgba(74,222,128,.4)', fontSize: '.58rem', letterSpacing: '.12em', marginTop: '.1rem' }}>{s.l}</div>
</div>
))}
</div>
{/* 홀짝 바 */}
<div style={{ maxWidth: 220, margin: '0 auto .875rem' }}>
<OddEvenBar odd={previewMetrics.odd} even={previewMetrics.even} />
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '.35rem', background: 'rgba(0,0,0,.2)', borderRadius: '2rem', padding: '.25rem .85rem' }}>
<div style={{ width: 5, height: 5, borderRadius: '50%', background: previewSource === 'nas' ? '#4ade80' : '#94a3b8' }} />
<span style={{ color: 'rgba(255,255,255,.25)', fontSize: '.62rem', fontFamily: "'JetBrains Mono',monospace" }}>
{previewSource === 'nas' ? 'NAS · Monte Carlo' : 'Client · 5,000 iterations'}
</span>
</div>
</div>
</div>
)}
{previewState === 'error' && (
<p style={{ color: '#f87171', fontSize: '.8rem', marginBottom: '1rem', background: 'rgba(239,68,68,.08)', border: '1px solid rgba(239,68,68,.2)', borderRadius: '.75rem', padding: '.65rem 1rem', textAlign: 'center' }}>
. .
</p>
)}
{/* 버튼 */}
<div style={{ textAlign: 'center', marginTop: '1.25rem' }}>
{!previewUsed ? (
<button
className="preview-btn"
onClick={handlePreview}
disabled={previewState === 'loading'}
style={{ background: 'linear-gradient(135deg,#4ade80,#22c55e)', color: '#052e16', border: 'none', borderRadius: '1rem', padding: '.9rem 2.5rem', fontSize: '.95rem', fontWeight: 800, cursor: previewState === 'loading' ? 'not-allowed' : 'pointer', transition: 'all .2s', boxShadow: '0 4px 24px rgba(34,197,94,.3)', display: 'inline-flex', alignItems: 'center', gap: '.5rem' }}>
{previewState === 'loading' ? (
<><div style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid rgba(5,46,22,.3)', borderTop: '2px solid rgba(5,46,22,.7)', animation: 'spin .7s linear infinite' }} /> ...</>
) : (
<>🎰 </>
)}
</button>
) : (
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '.4rem', background: 'rgba(74,222,128,.08)', border: '1px solid rgba(74,222,128,.18)', borderRadius: '2rem', padding: '.5rem 1.25rem', animation: 'badgePop .5s cubic-bezier(.34,1.56,.64,1)' }}>
<span style={{ color: '#4ade80', fontSize: '.78rem', fontWeight: 700 }}> </span>
</div>
)}
{previewUsed && !isSubscribed && (
<p style={{ color: 'rgba(255,255,255,.2)', fontSize: '.72rem', margin: '.75rem 0 0', textAlign: 'center' }}>
</p>
)}
</div>
</div>
</div>
</div>
{/* ════════════════════════════════════════════════
구독자 전용 섹션 (블러 게이트)
════════════════════════════════════════════════ */}
<div style={{ position: 'relative' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.75rem', marginBottom: '.875rem' }}>
<div style={{ display: 'inline-flex', alignItems: 'center', gap: '.4rem', background: 'rgba(251,191,36,.08)', border: '1px solid rgba(251,191,36,.22)', borderRadius: '2rem', padding: '.3rem .85rem' }}>
<svg width={9} height={9} viewBox="0 0 24 24" fill="#fbbf24"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" /></svg>
<span style={{ color: '#fbbf24', fontSize: '.68rem', fontWeight: 700, letterSpacing: '.1em', fontFamily: "'JetBrains Mono',monospace" }}>PREMIUM</span>
</div>
<span style={{ color: 'rgba(255,255,255,.2)', fontSize: '.72rem' }}>
{isSubscribed ? '프리미엄 번호 추천 · 통계 분석' : '구독 시 제공되는 기능 미리보기'}
</span>
</div>
{/* 프리미엄 컨텐츠 */}
<div style={{ position: 'relative', filter: isSubscribed ? 'none' : 'blur(4px)', opacity: isSubscribed ? 1 : 0.45, pointerEvents: isSubscribed ? 'auto' : 'none', transition: 'filter .3s,opacity .3s', userSelect: isSubscribed ? 'auto' : 'none' }}>
{/* ── 번호 생성 메인 카드 ── */}
<div style={{ background: 'linear-gradient(145deg,rgba(255,255,255,.04),rgba(251,191,36,.02))', border: '1px solid rgba(251,191,36,.15)', borderRadius: '1.75rem', padding: '2rem', marginBottom: '1.25rem', position: 'relative', overflow: 'hidden' }}>
{/* 배경 그리드 */}
<div style={{ position: 'absolute', inset: 0, backgroundImage: 'linear-gradient(rgba(251,191,36,.02) 1px,transparent 1px),linear-gradient(90deg,rgba(251,191,36,.02) 1px,transparent 1px)', backgroundSize: '60px 60px', pointerEvents: 'none', borderRadius: '1.75rem' }} />
<div style={{ position: 'absolute', top: -80, right: -80, width: 260, height: 260, borderRadius: '50%', background: 'radial-gradient(circle,rgba(251,191,36,.06),transparent)', pointerEvents: 'none' }} />
<div style={{ position: 'relative' }}>
{/* 분석 지표 배너 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '.65rem', marginBottom: '1.75rem' }}>
{[
{ icon: '⚡', val: latestRun ? `${(latestRun.total_generated / 10000).toFixed(0)}만 회` : '10만 회', label: '시뮬레이션', sub: 'Monte Carlo Runs', color: '#fbbf24' },
{ icon: '📊', val: totalDraws ? `${totalDraws.toLocaleString()}` : '1,130+', label: '분석 회차', sub: 'Historical Draws', color: '#06b6d4' },
{ icon: '🎯', val: `${combos.length} / ${MAX_COMBOS}`, label: '생성 조합', sub: 'Generated Combos', color: '#a78bfa' },
].map(s => (
<div key={s.label} style={{ background: 'rgba(0,0,0,.2)', border: `1px solid ${s.color}18`, borderRadius: '.875rem', padding: '.875rem 1rem', textAlign: 'center', position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: '50%', transform: 'translateX(-50%)', width: '60%', height: 2, background: `linear-gradient(90deg,transparent,${s.color}50,transparent)` }} />
<div style={{ fontSize: '1.1rem', marginBottom: '.25rem' }}>{s.icon}</div>
<div style={{ color: s.color, fontSize: 'clamp(.85rem,2vw,1.15rem)', fontWeight: 900, fontFamily: "'JetBrains Mono',monospace" }}>{s.val}</div>
<div style={{ color: 'rgba(255,255,255,.3)', fontSize: '.65rem', marginTop: '.2rem' }}>{s.label}</div>
<div style={{ color: `${s.color}50`, fontSize: '.55rem', marginTop: '.1rem', fontFamily: "'JetBrains Mono',monospace", letterSpacing: '.06em' }}>{s.sub}</div>
</div>
))}
</div>
{/* 전략 선택 */}
<div style={{ marginBottom: '1.25rem' }}>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.55rem', letterSpacing: '.15em', color: 'rgba(255,255,255,.2)', textTransform: 'uppercase', marginBottom: '.5rem', textAlign: 'center' }}>
GENERATION STRATEGY
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '.5rem' }}>
{(Object.entries(STRATEGY_INFO) as [Strategy, typeof STRATEGY_INFO[Strategy]][]).map(([key, info]) => (
<button key={key} onClick={() => setStrategy(key)} disabled={isProLoading}
className="mode-tab"
style={{
background: strategy === key ? info.color : 'rgba(0,0,0,.2)',
border: `1px solid ${strategy === key ? info.accentColor + '50' : 'rgba(255,255,255,.05)'}`,
borderRadius: '.75rem', padding: '.65rem .5rem', textAlign: 'center',
cursor: 'pointer', transition: 'all .2s',
}}>
<div style={{ fontSize: '1.1rem', marginBottom: '.2rem' }}>{info.icon}</div>
<div style={{ color: strategy === key ? info.accentColor : 'rgba(255,255,255,.3)', fontSize: '.72rem', fontWeight: 700, fontFamily: "'Noto Sans KR',sans-serif", marginBottom: '.15rem' }}>
{info.label}
</div>
<div style={{ color: 'rgba(255,255,255,.18)', fontSize: '.58rem', lineHeight: 1.4, fontFamily: "'Noto Sans KR',sans-serif" }}>
{info.desc}
</div>
</button>
))}
</div>
</div>
{/* 모드 탭 */}
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.5rem' }}>
<div style={{ display: 'inline-flex', background: 'rgba(0,0,0,.3)', borderRadius: '.875rem', padding: '.25rem', gap: '.25rem' }}>
{(['single', 'batch'] as const).map(mode => (
<button key={mode} className="mode-tab" onClick={() => setGenMode(mode)} disabled={isProLoading}
style={{ background: genMode === mode ? 'linear-gradient(135deg,#fbbf24,#f59e0b)' : 'transparent', color: genMode === mode ? '#78350f' : 'rgba(253,230,138,.4)', border: 'none', borderRadius: '.6rem', padding: '.45rem 1.25rem', fontSize: '.78rem', fontWeight: genMode === mode ? 800 : 600, cursor: 'pointer', fontFamily: "'Noto Sans KR',sans-serif" }}>
{mode === 'single' ? '단일 생성' : `${Math.min(5, MAX_COMBOS - combos.length)}개 배치`}
</button>
))}
</div>
</div>
{/* 볼 디스플레이 */}
<div style={{ minHeight: 110, display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: '1.5rem', flexWrap: 'wrap', gap: '.85rem' }}>
{isProLoading ? (
SPIN_NUMS.map((n, i) => <SpinBall key={i} n={n} delay={i * 100} />)
) : latestCombo ? (
latestCombo.numbers.map((n, i) => <LottoBall key={i} n={n} size={72} bounce delay={i * 120} highlight={latestCombo.overlap?.includes(n)} />)
) : (
Array.from({ length: 6 }, (_, i) => (
<div key={i} style={{ width: 72, height: 72, borderRadius: '50%', border: '2px dashed rgba(251,191,36,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'rgba(251,191,36,.12)', fontSize: '1.5rem', fontWeight: 900, animation: `float ${2 + i * .28}s ease-in-out infinite`, animationDelay: `${i * .18}s`, fontFamily: "'JetBrains Mono',monospace" }}>?</div>
))
)}
</div>
{/* 메트릭 + 홀짝 바 */}
{latestCombo?.metrics && !isProLoading && (
<div style={{ animation: 'slideUp .4s ease' }}>
<div style={{ display: 'flex', gap: '.65rem', justifyContent: 'center', marginBottom: '.875rem', flexWrap: 'wrap' }}>
{[
{ l: 'SUM', v: latestCombo.metrics.sum, good: latestCombo.metrics.sum >= 100 && latestCombo.metrics.sum <= 175 },
{ l: 'ODD', v: latestCombo.metrics.odd },
{ l: 'EVEN', v: latestCombo.metrics.even },
{ l: 'RANGE', v: latestCombo.metrics.range },
].map(s => (
<div key={s.l} style={{ background: 'rgba(251,191,36,.06)', border: '1px solid rgba(251,191,36,.12)', borderRadius: '.625rem', padding: '.4rem .875rem', textAlign: 'center' }}>
<div style={{ color: '#fbbf24', fontSize: '.9rem', fontWeight: 800, fontFamily: "'JetBrains Mono',monospace" }}>{s.v}</div>
<div style={{ color: 'rgba(253,230,138,.35)', fontSize: '.58rem', letterSpacing: '.12em', marginTop: '.1rem' }}>{s.l}</div>
</div>
))}
</div>
<div style={{ maxWidth: 260, margin: '0 auto .5rem' }}>
<OddEvenBar odd={latestCombo.metrics.odd} even={latestCombo.metrics.even} />
</div>
{/* 추천 이유 */}
<div style={{ textAlign: 'center', marginBottom: '.75rem' }}>
<div style={{ display: 'inline-block', background: 'rgba(251,191,36,.06)', border: '1px solid rgba(251,191,36,.1)', borderRadius: '.625rem', padding: '.35rem .9rem' }}>
<span style={{ color: 'rgba(253,230,138,.5)', fontSize: '.68rem', fontFamily: "'JetBrains Mono',monospace" }}>
{latestCombo.metrics.sum >= 100 && latestCombo.metrics.sum <= 175
? '✓ 최적 합계 범위 (100~175)'
: '합계 기준 ±35 이내'
} · {latestCombo.metrics.odd}:{latestCombo.metrics.even}
</span>
</div>
</div>
</div>
)}
{isProLoading && (
<div style={{ textAlign: 'center', marginBottom: '1rem', animation: 'slideUp .3s ease' }}>
<div style={{ color: 'rgba(251,191,36,.55)', fontSize: '.82rem', marginBottom: '.4rem' }}>
{genMode === 'batch' ? `${Math.min(5, MAX_COMBOS - combos.length)}개 번호 조합 배치 생성 중...` : '몬테카를로 시뮬레이션으로 최적 번호 계산 중...'}
</div>
<div style={{ display: 'flex', justifyContent: 'center', gap: '.3rem' }}>
{[0, 1, 2].map(i => (
<div key={i} style={{ width: 4, height: 4, borderRadius: '50%', background: '#fbbf24', animation: 'dataFlow 1s ease-in-out infinite', animationDelay: `${i * .2}s` }} />
))}
</div>
</div>
)}
{proState === 'error' && (
<p style={{ color: '#f87171', fontSize: '.82rem', marginBottom: '1rem', background: 'rgba(239,68,68,.07)', border: '1px solid rgba(239,68,68,.18)', borderRadius: '.75rem', padding: '.75rem 1.25rem', textAlign: 'center' }}> {proError}</p>
)}
{/* 생성 버튼 */}
<div style={{ textAlign: 'center' }}>
<button className="gen-btn" onClick={handleGenerate} disabled={isProLoading || isMaxed}
style={{ background: isProLoading || isMaxed ? 'rgba(251,191,36,.06)' : 'linear-gradient(135deg,#fbbf24,#f59e0b,#d97706)', color: isProLoading || isMaxed ? 'rgba(251,191,36,.25)' : '#78350f', border: 'none', borderRadius: '1rem', padding: '1rem 3rem', fontSize: '1rem', fontWeight: 900, cursor: isProLoading || isMaxed ? 'not-allowed' : 'pointer', transition: 'all .2s cubic-bezier(.34,1.56,.64,1)', boxShadow: isProLoading || isMaxed ? 'none' : '0 4px 32px rgba(251,191,36,.3)', letterSpacing: '.02em', display: 'inline-flex', alignItems: 'center', gap: '.55rem', fontFamily: "'Noto Sans KR',sans-serif" }}>
{isProLoading ? (
<><div style={{ width: 16, height: 16, borderRadius: '50%', border: '2px solid rgba(251,191,36,.3)', borderTop: '2px solid rgba(251,191,36,.6)', animation: 'spin .7s linear infinite' }} /> ...</>
) : isMaxed ? '✓ 최대 조합 생성 완료' : (
<><svg width={16} height={16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" /></svg>{STRATEGY_INFO[strategy].icon} {genMode === 'batch' ? `${Math.min(5, MAX_COMBOS - combos.length)}개 배치` : '번호 생성'} · {STRATEGY_INFO[strategy].label}</>
)}
</button>
{isMaxed && (
<div style={{ marginTop: '.875rem' }}>
<button onClick={clearCombos} style={{ background: 'transparent', border: '1px solid rgba(251,191,36,.15)', color: 'rgba(251,191,36,.4)', borderRadius: '.75rem', padding: '.5rem 1.25rem', fontSize: '.75rem', cursor: 'pointer', fontFamily: "'Noto Sans KR',sans-serif" }}> </button>
</div>
)}
</div>
</div>
</div>
{/* ── 생성된 조합 목록 ── */}
{combos.length > 0 && (
<div style={{ marginBottom: '1.25rem' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '.875rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.6rem', letterSpacing: '.15em', color: 'rgba(255,255,255,.2)', textTransform: 'uppercase' }}>GENERATED COMBOS</div>
<div style={{ background: 'rgba(251,191,36,.1)', border: '1px solid rgba(251,191,36,.2)', borderRadius: '2rem', padding: '.1rem .5rem' }}>
<span style={{ color: '#fbbf24', fontSize: '.65rem', fontFamily: "'JetBrains Mono',monospace" }}>{combos.length}</span>
</div>
</div>
{combos.length > 1 && <button onClick={clearCombos} style={{ background: 'transparent', border: 'none', color: 'rgba(251,191,36,.3)', fontSize: '.72rem', cursor: 'pointer', padding: '.25rem .5rem', fontFamily: "'Noto Sans KR',sans-serif" }}> </button>}
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.625rem' }}>
{combos.map((c, idx) => {
const isLatest = idx === combos.length - 1;
return (
<div key={c.id} className="combo-card" style={{ background: isLatest ? 'linear-gradient(145deg,rgba(251,191,36,.07),rgba(249,115,22,.03))' : 'linear-gradient(145deg,rgba(255,255,255,.025),rgba(255,255,255,.01))', border: isLatest ? '1px solid rgba(251,191,36,.25)' : '1px solid rgba(255,255,255,.05)', borderRadius: '1.125rem', padding: '1rem 1.25rem', animationDelay: `${idx * 40}ms` }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '.75rem', flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.75rem', flexWrap: 'wrap' }}>
<div style={{ width: 24, height: 24, borderRadius: '50%', background: isLatest ? 'linear-gradient(135deg,#fbbf24,#f59e0b)' : 'rgba(255,255,255,.05)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '.65rem', fontWeight: 800, color: isLatest ? '#78350f' : 'rgba(255,255,255,.2)', flexShrink: 0, fontFamily: "'JetBrains Mono',monospace" }}>{idx + 1}</div>
<div style={{ display: 'flex', gap: '.4rem', flexWrap: 'wrap' }}>
{c.numbers.map((n, ni) => <LottoBall key={ni} n={n} size={38} highlight={c.overlap?.includes(n)} />)}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.3rem', alignItems: 'flex-end' }}>
{c.metrics && (
<>
<span style={{ color: 'rgba(251,191,36,.45)', fontSize: '.65rem', fontFamily: "'JetBrains Mono',monospace" }}>{c.metrics.sum} · {c.metrics.odd}{c.metrics.even}</span>
<div style={{ width: 100 }}>
<OddEvenBar odd={c.metrics.odd} even={c.metrics.even} />
</div>
</>
)}
<div style={{ color: 'rgba(255,255,255,.15)', fontSize: '.62rem', fontFamily: "'JetBrains Mono',monospace" }}>{c.createdAt.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}</div>
</div>
</div>
</div>
);
})}
</div>
</div>
)}
{/* ── 포트폴리오 요약 (2개 이상 생성 시) ── */}
{combos.length >= 2 && (() => {
const withMetrics = combos.filter(c => c.metrics);
if (withMetrics.length === 0) return null;
const avgSum = Math.round(withMetrics.reduce((a, c) => a + c.metrics!.sum, 0) / withMetrics.length);
const avgOdd = (withMetrics.reduce((a, c) => a + c.metrics!.odd, 0) / withMetrics.length).toFixed(1);
const allNums = combos.flatMap(c => c.numbers);
const zoneCount = [0,0,0,0,0];
allNums.forEach(n => { zoneCount[Math.min(Math.floor((n-1)/10),4)]++; });
const totalNums = allNums.length;
return (
<div style={{ background: 'linear-gradient(145deg,rgba(168,85,247,.06),rgba(99,102,241,.03))', border: '1px solid rgba(168,85,247,.15)', borderRadius: '1.25rem', padding: '1.25rem 1.5rem', marginBottom: '1.25rem', animation: 'slideUp .4s ease' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem', marginBottom: '1rem' }}>
<div style={{ width: 7, height: 7, borderRadius: '50%', background: '#a78bfa', boxShadow: '0 0 8px rgba(167,139,250,.7)' }} />
<span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.6rem', letterSpacing: '.15em', color: 'rgba(167,139,250,.7)', textTransform: 'uppercase' }}>
PORTFOLIO ANALYSIS · {combos.length}
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(120px,1fr))', gap: '.75rem', marginBottom: '.875rem' }}>
{[
{ l: '평균 합계', v: avgSum, sub: `목표 138`, color: '#fbbf24' },
{ l: '평균 홀수', v: `${avgOdd}`, sub: '권장 3개', color: '#f87171' },
{ l: '총 투자', v: `${(combos.length * 1000).toLocaleString()}`, sub: '회당 1,000원', color: '#4ade80' },
].map(s => (
<div key={s.l} style={{ background: 'rgba(0,0,0,.2)', borderRadius: '.75rem', padding: '.75rem', textAlign: 'center' }}>
<div style={{ color: s.color, fontSize: '.95rem', fontWeight: 800, fontFamily: "'JetBrains Mono',monospace" }}>{s.v}</div>
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.6rem', marginTop: '.15rem' }}>{s.l}</div>
<div style={{ color: `${s.color}50`, fontSize: '.55rem', marginTop: '.1rem', fontFamily: "'JetBrains Mono',monospace" }}>{s.sub}</div>
</div>
))}
</div>
{/* 구간 분포 */}
<div>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.55rem', letterSpacing: '.12em', color: 'rgba(255,255,255,.18)', marginBottom: '.4rem' }}>ZONE DISTRIBUTION</div>
<div style={{ display: 'flex', gap: '.3rem', alignItems: 'flex-end', height: 32 }}>
{zoneCount.map((cnt, i) => {
const pct = totalNums > 0 ? (cnt / totalNums) * 100 : 0;
const zoneColors = ['#fbbf24','#3b82f6','#ef4444','#9ca3af','#22c55e'];
const zoneLabels = ['1-10','11-20','21-30','31-40','41-45'];
return (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '.2rem' }}>
<div style={{ width: '100%', background: zoneColors[i], borderRadius: '2px 2px 0 0', opacity: .7, height: `${Math.max(4, pct * 0.8)}px`, transition: 'height .5s ease' }} />
<div style={{ color: 'rgba(255,255,255,.2)', fontSize: '.5rem', fontFamily: "'JetBrains Mono',monospace" }}>{zoneLabels[i]}</div>
</div>
);
})}
</div>
</div>
</div>
);
})()}
{/* ── Hot / Cold Numbers (인포그래픽 바) ── */}
{(hotStats.length > 0 || coldStats.length > 0) && (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(280px,1fr))', gap: '1rem', marginBottom: '1.25rem' }}>
{hotStats.length > 0 && (
<div style={{ background: 'linear-gradient(145deg,rgba(239,68,68,.06),rgba(239,68,68,.015))', border: '1px solid rgba(239,68,68,.13)', borderRadius: '1.25rem', padding: '1.5rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem', marginBottom: '1.25rem' }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#ef4444', boxShadow: '0 0 8px rgba(239,68,68,.7)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ color: '#fca5a5', fontSize: '.65rem', fontWeight: 700, letterSpacing: '.12em', fontFamily: "'JetBrains Mono',monospace" }}>HOT NUMBERS · </span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.1rem' }}>
{hotStats.map((s, i) => (
<div key={s.number} className="hot-row" style={{ animationDelay: `${i * 60}ms` }}>
<FreqBar number={s.number} zScore={s.z_score} gap={s.gap} isHot={true} />
</div>
))}
</div>
</div>
)}
{coldStats.length > 0 && (
<div style={{ background: 'linear-gradient(145deg,rgba(59,130,246,.06),rgba(59,130,246,.015))', border: '1px solid rgba(59,130,246,.13)', borderRadius: '1.25rem', padding: '1.5rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem', marginBottom: '1.25rem' }}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: '#3b82f6', boxShadow: '0 0 8px rgba(59,130,246,.7)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ color: '#93c5fd', fontSize: '.65rem', fontWeight: 700, letterSpacing: '.12em', fontFamily: "'JetBrains Mono',monospace" }}>COLD NUMBERS · </span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '.1rem' }}>
{coldStats.map((s, i) => (
<div key={s.number} className="hot-row" style={{ animationDelay: `${i * 60}ms` }}>
<FreqBar number={s.number} zScore={s.z_score} gap={s.gap} isHot={false} />
</div>
))}
</div>
</div>
)}
</div>
)}
{/* ── 시뮬레이션 정보 바 ── */}
{latestRun && (
<div style={{ background: 'rgba(255,255,255,.02)', border: '1px solid rgba(255,255,255,.05)', borderRadius: '1rem', padding: '1rem 1.5rem', display: 'flex', gap: '1.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '.5rem' }}>
<div style={{ width: 7, height: 7, borderRadius: '50%', background: '#4ade80', boxShadow: '0 0 8px rgba(74,222,128,.6)', animation: 'glowPulse 2s ease-in-out infinite' }} />
<span style={{ color: 'rgba(255,255,255,.25)', fontSize: '.65rem', fontWeight: 700, letterSpacing: '.1em', fontFamily: "'JetBrains Mono',monospace" }}>LATEST SIM · {latestRun.strategy.toUpperCase()}</span>
</div>
{[
{ l: 'Generated', v: `${latestRun.total_generated.toLocaleString()}` },
{ l: 'Avg Score', v: latestRun.avg_score.toFixed(4) },
{ l: 'Run At', v: new Date(latestRun.run_at).toLocaleString('ko-KR', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) },
].map(s => (
<div key={s.l}>
<span style={{ color: 'rgba(255,255,255,.18)', fontSize: '.65rem', fontFamily: "'JetBrains Mono',monospace" }}>{s.l}: </span>
<span style={{ color: 'rgba(255,255,255,.5)', fontSize: '.65rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}>{s.v}</span>
</div>
))}
</div>
)}
</div>
{/* ── 비구독자 잠금 오버레이 ── */}
{!isSubscribed && (
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 10, padding: '1rem' }}>
<div style={{ textAlign: 'center', maxWidth: 420, background: 'linear-gradient(145deg,rgba(4,16,43,.92),rgba(10,5,0,.88))', backdropFilter: 'blur(12px)', border: '1px solid rgba(251,191,36,.22)', borderRadius: '1.75rem', padding: '2.5rem 2rem', boxShadow: '0 32px 80px rgba(0,0,0,.6)' }}>
<div style={{ width: 64, height: 64, borderRadius: '50%', background: 'linear-gradient(135deg,rgba(251,191,36,.18),rgba(249,115,22,.08))', border: '1px solid rgba(251,191,36,.28)', margin: '0 auto 1.25rem', display: 'flex', alignItems: 'center', justifyContent: 'center', animation: 'pulse 3s ease-in-out infinite' }}>
<svg width={28} height={28} viewBox="0 0 24 24" fill="none" stroke="#fbbf24" strokeWidth={2}>
<rect x="3" y="11" width="18" height="11" rx="2" />
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
</svg>
</div>
<div style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.6rem', letterSpacing: '.18em', color: 'rgba(251,191,36,.4)', marginBottom: '.75rem' }}>SUBSCRIPTION REQUIRED</div>
<h3 style={{ color: '#fbbf24', fontSize: '1.15rem', fontWeight: 900, margin: '0 0 .625rem', letterSpacing: '-.01em' }}> </h3>
<p style={{ color: 'rgba(253,230,138,.45)', fontSize: '.8rem', lineHeight: 1.7, margin: '0 0 1.5rem' }}>
<strong style={{ color: '#fbbf24' }}></strong> 1 · <strong style={{ color: '#fbbf24' }}></strong> 3 · <strong style={{ color: '#fbbf24' }}></strong> <br />
/ · ·
</p>
<div style={{ display: 'flex', gap: '.75rem', justifyContent: 'center', flexWrap: 'wrap' }}>
<Link href="/services/lotto" style={{ display: 'inline-flex', alignItems: 'center', gap: '.4rem', background: 'linear-gradient(135deg,#fbbf24,#f59e0b)', color: '#78350f', padding: '.8rem 1.75rem', borderRadius: '.875rem', fontWeight: 800, fontSize: '.88rem', textDecoration: 'none', boxShadow: '0 4px 24px rgba(251,191,36,.3)' }}>
</Link>
<Link href="/login" style={{ display: 'inline-flex', alignItems: 'center', background: 'rgba(255,255,255,.05)', border: '1px solid rgba(255,255,255,.1)', color: 'rgba(255,255,255,.45)', padding: '.8rem 1.25rem', borderRadius: '.875rem', fontWeight: 600, fontSize: '.88rem', textDecoration: 'none' }}>
</Link>
</div>
</div>
</div>
)}
</div>
{/* ── 소셜 증거 패널 ── */}
<div style={{ background: 'rgba(255,255,255,.02)', border: '1px solid rgba(255,255,255,.04)', borderRadius: '1rem', padding: '1rem 1.5rem', marginTop: '1.75rem', display: 'flex', gap: '1.5rem', flexWrap: 'wrap', alignItems: 'center' }}>
<span style={{ fontFamily: "'JetBrains Mono',monospace", fontSize: '.55rem', letterSpacing: '.12em', color: 'rgba(255,255,255,.15)', textTransform: 'uppercase' }}>COMMUNITY</span>
{[
{ icon: '👥', val: '2,847', label: '이번 주 번호 생성' },
{ icon: '🎯', val: '3,241', label: '3개 일치 달성 누적' },
{ icon: '📊', val: `${hotNumbers.length + coldNumbers.length}`, label: '이번 회차 패턴 감지' },
].map(s => (
<div key={s.label} style={{ display: 'flex', alignItems: 'center', gap: '.4rem' }}>
<span style={{ fontSize: '.85rem' }}>{s.icon}</span>
<div>
<div style={{ color: 'rgba(255,255,255,.45)', fontSize: '.72rem', fontWeight: 700, fontFamily: "'JetBrains Mono',monospace" }}>{s.val}</div>
<div style={{ color: 'rgba(255,255,255,.15)', fontSize: '.58rem' }}>{s.label}</div>
</div>
</div>
))}
</div>
{/* ── Color Legend ── */}
<div style={{ background: 'rgba(255,255,255,.02)', border: '1px solid rgba(255,255,255,.04)', borderRadius: '1rem', padding: '.875rem 1.5rem', display: 'flex', gap: '1.25rem', flexWrap: 'wrap', alignItems: 'center', marginTop: '.75rem' }}>
<span style={{ color: 'rgba(255,255,255,.2)', fontSize: '.62rem', fontWeight: 700, letterSpacing: '.12em', fontFamily: "'JetBrains Mono',monospace" }}>BALL COLOR</span>
{[{ r: '110', c: '#fbbf24' }, { r: '1120', c: '#3b82f6' }, { r: '2130', c: '#ef4444' }, { r: '3140', c: '#9ca3af' }, { r: '4145', c: '#22c55e' }].map(item => (
<div key={item.r} style={{ display: 'flex', alignItems: 'center', gap: '.35rem' }}>
<div style={{ width: 9, height: 9, borderRadius: '50%', background: item.c, boxShadow: `0 0 6px ${item.c}80` }} />
<span style={{ color: 'rgba(255,255,255,.25)', fontSize: '.65rem', fontFamily: "'JetBrains Mono',monospace" }}>{item.r}</span>
</div>
))}
</div>
<p style={{ textAlign: 'center', color: 'rgba(255,255,255,.1)', fontSize: '.65rem', marginTop: '1.75rem', lineHeight: 1.7, fontFamily: "'JetBrains Mono',monospace" }}>
, .
</p>
</div>
</div>
</>
);
}