Unify vote-submit into single always-visible CTA card

- Collapse pick→form two-step into one card: email gate header +
  input + benefit checkbox + single purple submit button
- Purple (--share) for user submit action vs mint for AI/system
- Keep disabled CTA visible (opacity .72 + glow) to drive clicks
- Unify copy with header: "투표 제출하고 AI와 겨루기"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Haewon Kam 2026-06-18 09:42:57 +09:00
parent 7d4d50af40
commit c8431e65f3
3 changed files with 37 additions and 26 deletions

View File

@ -130,6 +130,15 @@ body {
0 10px 28px rgba(133, 252, 168, 0.28); 0 10px 28px rgba(133, 252, 168, 0.28);
} }
/* 사용자 액션(투표 제출) — 브랜드 포인트 퍼플 */
.btn-share {
background: var(--share);
color: #fff;
box-shadow:
0 0 0 1px rgba(166, 94, 255, 0.5),
0 12px 30px rgba(166, 94, 255, 0.35);
}
.gold-card { .gold-card {
background: background:
linear-gradient(180deg, rgba(94, 240, 180, 0.08), rgba(21, 169, 155, 0.03)), linear-gradient(180deg, rgba(94, 240, 180, 0.08), rgba(21, 169, 155, 0.03)),

View File

@ -15,7 +15,7 @@ import type {
import Countdown from "./Countdown"; import Countdown from "./Countdown";
import TeamFlag from "./TeamFlag"; import TeamFlag from "./TeamFlag";
type Step = "pick" | "form" | "done"; type Step = "form" | "done";
const MODEL_ICON: Record<ModelName, string> = { const MODEL_ICON: Record<ModelName, string> = {
GPT: "/icons/gpt.png", GPT: "/icons/gpt.png",
@ -46,7 +46,7 @@ export default function Arena({
const [outcome, setOutcome] = useState<Outcome | null>(null); const [outcome, setOutcome] = useState<Outcome | null>(null);
const [scoreA, setScoreA] = useState(2); const [scoreA, setScoreA] = useState(2);
const [scoreB, setScoreB] = useState(1); const [scoreB, setScoreB] = useState(1);
const [step, setStep] = useState<Step>("pick"); const [step, setStep] = useState<Step>("form");
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [notify, setNotify] = useState(true); const [notify, setNotify] = useState(true);
const [crowd, setCrowd] = useState<CrowdStats>(initialCrowd); const [crowd, setCrowd] = useState<CrowdStats>(initialCrowd);
@ -80,10 +80,7 @@ export default function Arena({
.map((p) => ({ model: p.model, exact: p.scoreA === scoreA && p.scoreB === scoreB })); .map((p) => ({ model: p.model, exact: p.scoreA === scoreA && p.scoreB === scoreB }));
}, [outcome, scoreA, scoreB, predictions]); }, [outcome, scoreA, scoreB, predictions]);
const submitPick = () => { const emailValid = EMAIL_RE.test(email.trim());
if (!outcome) return;
setStep("form");
};
const confirmSubmit = () => { const confirmSubmit = () => {
if (!EMAIL_RE.test(email.trim())) return; if (!EMAIL_RE.test(email.trim())) return;
setCrowd((c) => ({ setCrowd((c) => ({
@ -263,43 +260,45 @@ export default function Arena({
</section> </section>
)} )}
{/* ===== CTA 민트 #85FCA8 (진행 중일 때) ===== */} {/* ===== 투표 마감/오픈 전: 상태 버튼만 ===== */}
{!finished && step === "pick" && ( {!finished && disabled && (
<button <button
onClick={submitPick} disabled
disabled={!outcome || disabled} className="btn-mint mt-5 w-full rounded-2xl py-5 text-[20px] font-extrabold opacity-60"
className="btn-mint mt-5 w-full rounded-2xl py-5 text-[20px] font-extrabold transition active:scale-[0.99] disabled:opacity-40 disabled:shadow-none"
> >
{ctaLabel} {ctaLabel}
</button> </button>
)} )}
{/* ===== 폼: 이메일만 (D5, 닉네임 제거) ===== */} {/* ===== 투표 제출 (D5): 이메일 + 단일 CTA를 한 카드로 통합 ===== */}
{!finished && step === "form" && ( {!finished && !disabled && step === "form" && (
<div className="mt-5 space-y-2.5 rounded-2xl border border-[var(--line-d)] bg-[var(--bg2)] p-4"> <div className="mt-5 rounded-2xl border-2 border-[var(--green)]/60 bg-[var(--bg2)] p-4 shadow-[0_0_24px_rgba(74,255,160,0.08)]">
<div className="mb-2.5 text-center text-[15px] font-extrabold text-[var(--green)]">
{t.emailGate}
</div>
<input <input
type="email" type="email"
inputMode="email" inputMode="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder={t.emailPh} placeholder={t.emailPh}
className="w-full rounded-lg border border-[var(--line-d)] bg-[#0f1217] px-3 py-3 text-[15px] text-white outline-none focus:border-[var(--green)]" className="w-full rounded-xl border border-[var(--line-d)] bg-[#0f1217] px-4 py-3.5 text-[16px] text-white outline-none focus:border-[var(--green)]"
/> />
<label className="flex items-start gap-2 text-[13px] text-[var(--ink-muted)]"> <label className="mt-2.5 flex items-start gap-2 text-[13px] leading-snug text-[var(--ink-muted)]">
<input <input
type="checkbox" type="checkbox"
checked={notify} checked={notify}
onChange={(e) => setNotify(e.target.checked)} onChange={(e) => setNotify(e.target.checked)}
className="mt-0.5 accent-[var(--mint)]" className="mt-0.5 accent-[var(--share)]"
/> />
{t.notify} {t.notify}
</label> </label>
<button <button
onClick={confirmSubmit} onClick={confirmSubmit}
disabled={!EMAIL_RE.test(email.trim())} disabled={!emailValid}
className="btn-mint w-full rounded-xl py-3.5 text-[17px] font-extrabold transition active:scale-[0.99] disabled:opacity-40 disabled:shadow-none" className="btn-share mt-3.5 flex w-full items-center justify-center gap-2 rounded-2xl py-5 text-[19px] font-extrabold transition active:scale-[0.99] disabled:opacity-[0.72]"
> >
{t.submit} {t.submit} <span aria-hidden></span>
</button> </button>
</div> </div>
)} )}

View File

@ -62,6 +62,7 @@ type Dict = {
ctaResult: string; ctaResult: string;
ctaLocked: string; ctaLocked: string;
ctaOpens: (d: string) => string; ctaOpens: (d: string) => string;
emailGate: string;
emailPh: string; emailPh: string;
notify: string; notify: string;
submit: string; submit: string;
@ -121,9 +122,10 @@ export const DICT: Record<Lang, Dict> = {
ctaResult: "결과 보기", ctaResult: "결과 보기",
ctaLocked: "예측 마감 (경기 시작)", ctaLocked: "예측 마감 (경기 시작)",
ctaOpens: (d) => `투표 오픈 ${d}`, ctaOpens: (d) => `투표 오픈 ${d}`,
emailPh: "이메일 (결과·다음 경기 알림)", emailGate: "투표를 제출하려면 이메일을 입력하세요",
notify: "경기 결과가 나오면 알려주세요. 다음 경기·100만원 챌린지 소식도 받겠습니다.", emailPh: "이메일 입력 → 투표 결과·다음 경기 알림",
submit: "제출하고 AI와 겨루기", notify: "결과 알림 + 다음 경기 소식 + ₩100만 챌린지 기회까지 무료로 받기",
submit: "투표 제출하고 AI와 겨루기",
myPick: "내 예측", myPick: "내 예측",
sameAI: (m, exact) => sameAI: (m, exact) =>
`당신은 ${m}와 같은 선택입니다.${exact ? " 스코어까지 일치!" : ""} 나머지 AI는 생각이 다릅니다.`, `당신은 ${m}와 같은 선택입니다.${exact ? " 스코어까지 일치!" : ""} 나머지 AI는 생각이 다릅니다.`,
@ -180,9 +182,10 @@ export const DICT: Record<Lang, Dict> = {
ctaResult: "View result", ctaResult: "View result",
ctaLocked: "Closed (kickoff)", ctaLocked: "Closed (kickoff)",
ctaOpens: (d) => `Opens ${d}`, ctaOpens: (d) => `Opens ${d}`,
emailPh: "Email (result & next-match alerts)", emailGate: "Enter your email to submit your vote",
notify: "Notify me when the result is out. I'll also get next-match & ₩1,000,000 challenge news.", emailPh: "Your email → result & next-match alerts",
submit: "Submit & face the AI", notify: "Result alerts + next-match news + a shot at the ₩1,000,000 challenge — free",
submit: "Submit your vote & face the AI",
myPick: "My Pick", myPick: "My Pick",
sameAI: (m, exact) => sameAI: (m, exact) =>
`You matched ${m}.${exact ? " Exact score too!" : ""} The other AIs disagree.`, `You matched ${m}.${exact ? " Exact score too!" : ""} The other AIs disagree.`,