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
parent
7d4d50af40
commit
c8431e65f3
|
|
@ -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)),
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
15
lib/i18n.ts
15
lib/i18n.ts
|
|
@ -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.`,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue