252 lines
16 KiB
TypeScript
252 lines
16 KiB
TypeScript
"use client";
|
||
|
||
// ============================================================
|
||
// BEFORE — 최초 구축 버전 (다크 네이비 토큰 테마) · 개인 기록용 보존
|
||
// 002/003 리디자인 이전 모습. 자체 완결(explicit hex), 현재 globals와 독립.
|
||
// ============================================================
|
||
import { useEffect, useMemo, useState } from "react";
|
||
|
||
const C = {
|
||
bg: "#0a0e1a",
|
||
bg2: "#0d1322",
|
||
card: "#141b2d",
|
||
green: "#25e07f",
|
||
amber: "#ffb23e",
|
||
muted: "#8b94a7",
|
||
line: "#26304a",
|
||
gpt: "#25e07f",
|
||
claude: "#c08bff",
|
||
gemini: "#4d9bff",
|
||
};
|
||
|
||
const MATCH = {
|
||
teamA: { short: "한국", name: "Korea Republic", flag: "🇰🇷" },
|
||
teamB: { short: "체코", name: "Czechia", flag: "🇨🇿" },
|
||
kickoff: "2026-06-12T11:00:00+09:00",
|
||
lockAt: "2026-06-12T10:50:00+09:00",
|
||
venue: "Estadio Guadalajara",
|
||
};
|
||
const AIS = [
|
||
{ model: "GPT", color: C.gpt, outcome: "A", score: "2 - 1", conf: 62, reason: "전환 속도와 핵심 공격 자원에서 우위", out: "한국 승" },
|
||
{ model: "Claude", color: C.claude, outcome: "D", score: "1 - 1", conf: 41, reason: "체코 수비 조직력과 세트피스가 변수", out: "무승부" },
|
||
{ model: "Gemini", color: C.gemini, outcome: "B", score: "0 - 2", conf: 28, reason: "체코의 원정 폼과 역습 효율을 높게 평가", out: "체코 승" },
|
||
];
|
||
|
||
function useCountdown(iso: string) {
|
||
const [now, setNow] = useState<number | null>(null);
|
||
useEffect(() => {
|
||
setNow(Date.now());
|
||
const t = setInterval(() => setNow(Date.now()), 1000);
|
||
return () => clearInterval(t);
|
||
}, []);
|
||
if (now === null) return null;
|
||
const diff = Math.max(0, new Date(iso).getTime() - now);
|
||
return {
|
||
d: Math.floor(diff / 86400000),
|
||
h: Math.floor((diff % 86400000) / 3600000),
|
||
m: Math.floor((diff % 3600000) / 60000),
|
||
s: Math.floor((diff % 60000) / 1000),
|
||
};
|
||
}
|
||
const pad = (n: number) => String(n).padStart(2, "0");
|
||
|
||
export default function Before() {
|
||
const cd = useCountdown(MATCH.kickoff);
|
||
const [outcome, setOutcome] = useState<string | null>(null);
|
||
const [a, setA] = useState(1);
|
||
const [b, setB] = useState(0);
|
||
const [step, setStep] = useState<"pick" | "form" | "done">("pick");
|
||
const [nick, setNick] = useState("");
|
||
const [total, setTotal] = useState(1284);
|
||
const [dist, setDist] = useState({ A: 48, D: 27, B: 25 });
|
||
|
||
const matched = useMemo(() => AIS.filter((p) => p.outcome === outcome).map((p) => p.model), [outcome]);
|
||
|
||
const wrap: React.CSSProperties = { background: C.bg, color: "#fff", minHeight: "100dvh" };
|
||
const card: React.CSSProperties = { background: C.card, border: `1px solid ${C.line}`, borderRadius: 12 };
|
||
|
||
return (
|
||
<div style={wrap}>
|
||
{/* 기록용 라벨 */}
|
||
<div style={{ background: "#1c2640", color: C.green, textAlign: "center", fontSize: 11, fontWeight: 700, padding: "6px" }}>
|
||
BEFORE — 최초 구축 버전 (기록용) · 현재 버전은 <a href="/" style={{ color: "#fff", textDecoration: "underline" }}>/ 에서</a>
|
||
</div>
|
||
|
||
{/* Sticky Top */}
|
||
<div style={{ position: "sticky", top: 0, zIndex: 50, background: "rgba(10,14,26,0.9)", borderBottom: `1px solid ${C.line}`, backdropFilter: "blur(6px)" }}>
|
||
<div style={{ maxWidth: 480, margin: "0 auto", padding: "0 16px" }}>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "10px 0" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ fontSize: 13, fontWeight: 800, color: C.green }}>TriplePick</span>
|
||
<span style={{ fontSize: 11, color: C.muted }}>🇰🇷 한국 vs 체코 🇨🇿</span>
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
||
<div style={{ textAlign: "right", lineHeight: 1 }}>
|
||
<div style={{ fontSize: 9, color: C.muted }}>킥오프까지</div>
|
||
<div style={{ fontSize: 12, fontWeight: 700, fontVariantNumeric: "tabular-nums" }}>
|
||
{cd ? `${cd.d}일 ${pad(cd.h)}:${pad(cd.m)}:${pad(cd.s)}` : "—"}
|
||
</div>
|
||
</div>
|
||
<button onClick={() => document.getElementById("pick")?.scrollIntoView({ behavior: "smooth" })} style={{ background: C.green, color: "#06210f", fontSize: 12, fontWeight: 700, borderRadius: 6, padding: "6px 12px", border: 0 }}>
|
||
내 픽하기
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div style={{ fontSize: 10, color: C.muted, paddingBottom: 6 }}>● {total.toLocaleString()}명 픽 완료</div>
|
||
</div>
|
||
</div>
|
||
|
||
<main style={{ maxWidth: 480, margin: "0 auto", padding: "0 16px 64px" }}>
|
||
{/* Hero */}
|
||
<header style={{ paddingTop: 20, paddingBottom: 12, textAlign: "center" }}>
|
||
<div style={{ fontSize: 20, fontWeight: 800 }}>Triple Pick <span style={{ color: C.green }}>2026</span></div>
|
||
<div style={{ fontSize: 12, color: C.muted }}>AI Prediction Arena</div>
|
||
<h1 style={{ fontSize: 22, fontWeight: 800, marginTop: 16, lineHeight: 1.35 }}>
|
||
한국 첫 경기,<br /><span style={{ color: C.green }}>AI의 선택은 갈렸다</span>
|
||
</h1>
|
||
<p style={{ fontSize: 12.5, color: C.muted, marginTop: 8, lineHeight: 1.6 }}>
|
||
같은 데이터를 본 AI 셋이 서로 다른 예측을 했습니다.<br />당신의 픽을 찍고, 경기 끝나면 누가 맞았는지 확인하세요.
|
||
</p>
|
||
</header>
|
||
|
||
{/* Match card */}
|
||
<section style={{ ...card, padding: 16, marginTop: 12 }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", fontSize: 10, color: C.muted }}>
|
||
<span style={{ border: `1px solid ${C.line}`, borderRadius: 999, padding: "2px 8px", color: C.green, fontWeight: 600 }}>Match 01</span>
|
||
<span>Matchday Prediction</span>
|
||
</div>
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr auto 1fr", alignItems: "center", gap: 8, marginTop: 12 }}>
|
||
<Team flag={MATCH.teamA.flag} short={MATCH.teamA.short} name={MATCH.teamA.name} />
|
||
<div style={{ fontSize: 18, fontWeight: 800, color: C.muted }}>VS</div>
|
||
<Team flag={MATCH.teamB.flag} short={MATCH.teamB.short} name={MATCH.teamB.name} />
|
||
</div>
|
||
<div style={{ marginTop: 12, textAlign: "center", fontSize: 11, color: C.muted }}>6.12(금) 11:00 KST · Group A</div>
|
||
<div style={{ marginTop: 2, textAlign: "center", fontSize: 10, color: C.muted }}>{MATCH.venue}</div>
|
||
</section>
|
||
|
||
{/* AI predictions */}
|
||
<section style={{ marginTop: 20 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 8 }}>
|
||
<h2 style={{ fontSize: 14, fontWeight: 800 }}>AI의 예측</h2>
|
||
<span style={{ fontSize: 10, color: C.muted }}>같은 데이터 · 다른 답</span>
|
||
</div>
|
||
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||
{AIS.map((p) => (
|
||
<div key={p.model} style={{ ...card, padding: 12 }}>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<span style={{ width: 28, height: 28, display: "grid", placeItems: "center", borderRadius: 6, fontSize: 11, fontWeight: 800, background: `${p.color}2e`, color: p.color }}>{p.model[0]}</span>
|
||
<div>
|
||
<div style={{ fontSize: 13, fontWeight: 700 }}>{p.model}</div>
|
||
<div style={{ fontSize: 10, color: C.muted, marginTop: 2 }}>{p.out} 예측</div>
|
||
</div>
|
||
</div>
|
||
<div style={{ textAlign: "right" }}>
|
||
<div style={{ fontSize: 16, fontWeight: 800, fontVariantNumeric: "tabular-nums" }}>{p.score}</div>
|
||
<div style={{ fontSize: 10, color: C.muted }}>확신도 {p.conf}%</div>
|
||
</div>
|
||
</div>
|
||
<div style={{ marginTop: 8, height: 6, borderRadius: 999, background: C.bg2, overflow: "hidden" }}>
|
||
<div style={{ width: `${p.conf}%`, height: "100%", background: p.color, borderRadius: 999 }} />
|
||
</div>
|
||
<p style={{ marginTop: 8, fontSize: 11, color: C.muted }}>{p.reason} <span style={{ opacity: 0.6 }}>· 기준 2026-06-08</span></p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Your pick */}
|
||
<section id="pick" style={{ marginTop: 20, scrollMarginTop: 80 }}>
|
||
<h2 style={{ fontSize: 14, fontWeight: 800, marginBottom: 8 }}>당신의 선택 <span style={{ color: "#a65eff" }}>🟣</span></h2>
|
||
<div style={{ ...card, padding: 16 }}>
|
||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 6, background: C.bg2, borderRadius: 8, padding: 4 }}>
|
||
{([["A", "한국 승"], ["D", "무승부"], ["B", "체코 승"]] as [string, string][]).map(([v, l]) => (
|
||
<button key={v} onClick={() => setOutcome(v)} style={{ borderRadius: 6, padding: "8px 0", fontSize: 12.5, fontWeight: 700, border: 0, background: outcome === v ? C.green : "transparent", color: outcome === v ? "#06210f" : C.muted }}>{l}</button>
|
||
))}
|
||
</div>
|
||
<div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 12, marginTop: 16 }}>
|
||
<Stepper label="한국" value={a} set={setA} c={C} />
|
||
<span style={{ fontSize: 18, fontWeight: 800, color: C.muted, paddingTop: 20 }}>:</span>
|
||
<Stepper label="체코" value={b} set={setB} c={C} />
|
||
</div>
|
||
{step === "pick" && (
|
||
<button onClick={() => outcome && setStep("form")} disabled={!outcome} style={{ marginTop: 16, width: "100%", borderRadius: 8, padding: "12px 0", fontSize: 15, fontWeight: 800, border: 0, background: C.green, color: "#06210f", opacity: outcome ? 1 : 0.4 }}>AI와 겨루기</button>
|
||
)}
|
||
{step === "form" && (
|
||
<div style={{ marginTop: 16, display: "flex", flexDirection: "column", gap: 8 }}>
|
||
<input value={nick} onChange={(e) => setNick(e.target.value)} placeholder="닉네임 (2~16자)" style={{ width: "100%", borderRadius: 8, border: `1px solid ${C.line}`, background: C.bg2, padding: "10px 12px", fontSize: 13, color: "#fff" }} />
|
||
<button onClick={() => { if (nick.trim().length >= 2) { setTotal((t) => t + 1); setStep("done"); } }} disabled={nick.trim().length < 2} style={{ width: "100%", borderRadius: 8, padding: "12px 0", fontSize: 15, fontWeight: 800, border: 0, background: C.green, color: "#06210f", opacity: nick.trim().length >= 2 ? 1 : 0.4 }}>제출하고 AI와 겨루기</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
{step === "done" && outcome && (
|
||
<div style={{ ...card, border: `1px solid ${C.green}80`, padding: 16, marginTop: 12 }}>
|
||
<div style={{ fontSize: 11, color: C.muted }}>내 예측</div>
|
||
<div style={{ fontSize: 20, fontWeight: 800, marginTop: 2 }}>한국 {a}-{b} 체코 <span style={{ fontSize: 13, color: C.green }}>{outcome === "A" ? "한국 승" : outcome === "B" ? "체코 승" : "무승부"}</span></div>
|
||
<p style={{ fontSize: 12.5, color: "#cfd6e2", marginTop: 8, lineHeight: 1.6 }}>
|
||
{matched.length ? <>당신은 <b>{matched.join("·")}</b>와 같은 선택입니다. 나머지 AI는 생각이 다릅니다.</> : <>당신은 AI 셋 모두와 다른 선택을 했습니다. 소신 픽! 🔥</>}<br />경기 끝나면 누가 맞았는지 알려드릴게요.
|
||
</p>
|
||
<button style={{ marginTop: 12, width: "100%", borderRadius: 8, padding: "10px 0", fontSize: 13, fontWeight: 700, border: 0, background: C.green, color: "#06210f" }}>내 픽 공유하기</button>
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{/* Crowd (세로 바) */}
|
||
<section style={{ marginTop: 20 }}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}>
|
||
<h2 style={{ fontSize: 14, fontWeight: 800 }}>Crowd Pick</h2>
|
||
<span style={{ fontSize: 10, color: C.muted }}>● {total.toLocaleString()}명 참여</span>
|
||
</div>
|
||
<div style={{ ...card, padding: 16, display: "flex", flexDirection: "column", gap: 10 }}>
|
||
{([["한국 승", dist.A], ["무승부", dist.D], ["체코 승", dist.B]] as [string, number][]).map(([l, v]) => (
|
||
<div key={l}>
|
||
<div style={{ display: "flex", justifyContent: "space-between", fontSize: 11, marginBottom: 4 }}><span style={{ color: C.muted }}>{l}</span><span style={{ fontWeight: 700 }}>{v}%</span></div>
|
||
<div style={{ height: 8, borderRadius: 999, background: C.bg2, overflow: "hidden" }}><div style={{ width: `${v}%`, height: "100%", background: C.green, borderRadius: 999 }} /></div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
{/* Prize */}
|
||
<section style={{ marginTop: 20 }}>
|
||
<div style={{ ...card, padding: 16 }}>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}><span style={{ fontSize: 18 }}>🏆</span><h2 style={{ fontSize: 14, fontWeight: 800 }}>글로벌 축구 축전 끝까지 맞히면 100만 원</h2></div>
|
||
<p style={{ marginTop: 6, fontSize: 12, color: C.muted, lineHeight: 1.6 }}>AI 이기고 끝까지 살아남으면 100만 원. 매 경기 픽하고, 최종 우승팀과 결승 스코어까지 맞히면 도전 자격.</p>
|
||
<button style={{ marginTop: 12, width: "100%", borderRadius: 8, padding: "10px 0", fontSize: 13, fontWeight: 700, background: `${C.green}1a`, color: C.green, border: `1px solid ${C.green}66` }}>100만원 도전하기</button>
|
||
</div>
|
||
</section>
|
||
|
||
{/* Footer */}
|
||
<footer style={{ marginTop: 32, borderTop: `1px solid ${C.line}`, paddingTop: 20 }}>
|
||
<p style={{ fontSize: 11, color: C.muted, lineHeight: 1.6 }}>이 캠페인은 AIO2O가 운영하는 AI 예측 실험입니다. GPT·Claude·Gemini에 동일한 경기 데이터를 제공하고 모델별 예측 차이를 기록합니다.</p>
|
||
<p style={{ fontSize: 10, color: `${C.muted}cc`, marginTop: 8, lineHeight: 1.6 }}>본 콘텐츠는 스포츠 분석·엔터테인먼트 목적의 예측 게임이며 베팅·도박을 권유하지 않습니다. AI 예측은 실제 결과를 보장하지 않습니다.</p>
|
||
<div style={{ marginTop: 12, display: "flex", justifyContent: "space-between", fontSize: 10, color: C.muted }}><span>© 2026 TriplePick · 운영 AIO2O</span><span>@triplepickai</span></div>
|
||
</footer>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function Team({ flag, short, name }: { flag: string; short: string; name: string }) {
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 6 }}>
|
||
<div style={{ fontSize: 40, lineHeight: 1 }}>{flag}</div>
|
||
<div style={{ fontSize: 13, fontWeight: 700 }}>{short}</div>
|
||
<div style={{ fontSize: 9, color: "#8b94a7" }}>{name}</div>
|
||
</div>
|
||
);
|
||
}
|
||
function Stepper({ label, value, set, c }: { label: string; value: number; set: (n: number) => void; c: typeof C }) {
|
||
const btn: React.CSSProperties = { width: 36, height: 36, display: "grid", placeItems: "center", borderRadius: 8, border: `1px solid ${c.line}`, background: c.bg2, color: "#fff", fontSize: 18, fontWeight: 700 };
|
||
return (
|
||
<div style={{ display: "flex", flexDirection: "column", alignItems: "center" }}>
|
||
<div style={{ fontSize: 11, color: c.muted, marginBottom: 4 }}>{label}</div>
|
||
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
||
<button onClick={() => set(Math.max(0, value - 1))} style={btn}>−</button>
|
||
<div style={{ width: 36, textAlign: "center", fontSize: 22, fontWeight: 800 }}>{value}</div>
|
||
<button onClick={() => set(Math.min(9, value + 1))} style={btn}>+</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|