Refine ranking placement, visibility, and consistency

- Move cumulative ranking above the gold ₩1M card (ranking → challenge flow)
- Drop redundant "전체 보기" link (inline folding shows full Top 10);
  /leaderboard route kept for direct access
- Make fold toggle purple + match its size to the tab buttons; enlarge
  title/tab/row text for readability
- Match email input & submit button radius to the ranking box (rounded-xl)
- Leaderboard full page: top button = "투표로 돌아가기" (history back, home fallback)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Haewon Kam 2026-06-18 16:47:57 +09:00
parent 965bd374c3
commit 996d5faaf4
6 changed files with 58 additions and 34 deletions

View File

@ -20,12 +20,12 @@ export default async function LeaderboardPage({
return (
<main className="shell">
<Hero back />
<Hero back backHistory />
<p className="mt-6 text-center text-[13px] leading-relaxed text-[var(--ink-muted)]">
{t.goldDesc}
</p>
{/* 전체 페이지: 펼친 상태(폴딩 토글·전체보기 링크 숨김) */}
<Leaderboard lang={lang} defaultOpen showAllLink={false} />
{/* 전체 페이지: 펼친 상태(폴딩 토글 숨김) */}
<Leaderboard lang={lang} defaultOpen />
<Footer />
</main>
);

View File

@ -340,7 +340,7 @@ export default function Arena({
value={email}
onChange={(e) => onEmailChange(e.target.value)}
placeholder={t.emailPh}
className="w-full rounded-2xl border border-[var(--share)] bg-[#0f1217] px-4 py-3 text-[16px] text-white shadow-[0_0_0_2px_rgba(166,94,255,0.12)] outline-none focus:shadow-[0_0_0_2px_rgba(166,94,255,0.3)]"
className="w-full rounded-xl border border-[var(--share)] bg-[#0f1217] px-4 py-3 text-[16px] text-white shadow-[0_0_0_2px_rgba(166,94,255,0.12)] outline-none focus:shadow-[0_0_0_2px_rgba(166,94,255,0.3)]"
/>
<label className="mt-2.5 flex items-start gap-2 text-[13px] leading-snug text-[var(--ink-muted)]">
<input
@ -354,7 +354,7 @@ export default function Arena({
<button
onClick={confirmSubmit}
disabled={!emailValid}
className="btn-share mt-3.5 flex w-full items-center justify-center gap-1.5 rounded-2xl py-3 text-[15px] font-extrabold transition active:scale-[0.99] disabled:opacity-[0.72]"
className="btn-share mt-3.5 flex w-full items-center justify-center gap-1.5 rounded-xl py-3 text-[15px] font-extrabold transition active:scale-[0.99] disabled:opacity-[0.72]"
>
{t.submit} <span aria-hidden></span>
</button>
@ -459,6 +459,9 @@ export default function Arena({
</section>
)}
{/* ===== 누적 랭킹 TOP 10 (골드 카드 위 · 폴딩) — 랭킹→상금 도전 흐름 ===== */}
<Leaderboard lang={lang} />
{/* ===== 골드 상금 (003) ===== */}
<section className="gold-card mt-5 rounded-2xl p-5">
<div className="flex items-center justify-between gap-3">
@ -477,9 +480,6 @@ export default function Arena({
</div>
</section>
{/* ===== 누적 랭킹 TOP 10 (골드 카드 직후 · 폴딩) ===== */}
<Leaderboard lang={lang} />
{/* ===== Crowd Pick (003) — 보팅 비율(≠ AI 승리 확률) ===== */}
<section className="mt-6">
<div className="mb-2.5 text-[20px] font-extrabold">

27
components/BackButton.tsx Normal file
View File

@ -0,0 +1,27 @@
"use client";
import { useRouter } from "next/navigation";
// 뒤로가기 — 직전 페이지(예: 투표하던 매치 페이지)로 복귀.
// 히스토리가 없으면(직접 진입) 홈으로 폴백.
export default function BackButton({
label,
home,
}: {
label: string;
home: string;
}) {
const router = useRouter();
const goBack = () => {
if (typeof window !== "undefined" && window.history.length > 1) router.back();
else router.push(home);
};
return (
<button
onClick={goBack}
className="flex items-center gap-1.5 rounded-full border border-white/12 bg-white/8 px-[18px] py-[9px] text-[18px] font-bold text-white/85 active:scale-95"
>
{label}
</button>
);
}

View File

@ -1,6 +1,7 @@
import Link from "next/link";
import ShareButton from "./ShareButton";
import LangSwitch from "./LangSwitch";
import BackButton from "./BackButton";
import { type Lang, dict } from "@/lib/i18n";
type Share = { url: string; title: string; text: string };
@ -8,10 +9,12 @@ type Share = { url: string; title: string; text: string };
// 브랜드 헤더 (대시보드·상세 공용). 경기별 후킹 카피는 상세 페이지에서 별도 노출.
export default function Hero({
back = false,
backHistory = false,
share,
lang = "ko",
}: {
back?: boolean;
backHistory?: boolean; // true면 홈 대신 직전 페이지로 복귀(뒤로가기)
share?: Share;
lang?: Lang;
}) {
@ -21,12 +24,16 @@ export default function Hero({
<header className="pt-5 text-center">
{back && (
<div className="mb-3 flex items-center justify-between">
<Link
href={home}
className="flex items-center gap-1.5 rounded-full border border-white/12 bg-white/8 px-[18px] py-[9px] text-[18px] font-bold text-white/85 active:scale-95"
>
{t.back}
</Link>
{backHistory ? (
<BackButton label={t.backPrev} home={home} />
) : (
<Link
href={home}
className="flex items-center gap-1.5 rounded-full border border-white/12 bg-white/8 px-[18px] py-[9px] text-[18px] font-bold text-white/85 active:scale-95"
>
{t.back}
</Link>
)}
{share && (
<ShareButton {...share} label={t.share} copiedLabel={t.shareCopied} />
)}

View File

@ -1,7 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { type Lang, dict } from "@/lib/i18n";
import {
fetchLeaderboard,
@ -14,11 +13,9 @@ import {
export default function Leaderboard({
lang = "ko",
defaultOpen = false,
showAllLink = true,
}: {
lang?: Lang;
defaultOpen?: boolean;
showAllLink?: boolean;
}) {
const t = dict(lang);
const [open, setOpen] = useState(defaultOpen);
@ -37,17 +34,7 @@ export default function Leaderboard({
return (
<section className="mt-5 rounded-2xl border border-[var(--line-d)] bg-[var(--bg2)] p-4">
<div className="flex items-center justify-between gap-2">
<h3 className="text-[16px] font-extrabold">{t.lbTitle}</h3>
{showAllLink && (
<Link
href={lang === "en" ? "/leaderboard?lang=en" : "/leaderboard"}
className="shrink-0 text-[12px] font-bold text-[var(--share)]"
>
{t.lbAll}
</Link>
)}
</div>
<h3 className="text-[19px] font-extrabold">{t.lbTitle}</h3>
{open && (
<>
@ -62,7 +49,7 @@ export default function Leaderboard({
<button
key={val}
onClick={() => setTab(val)}
className={`rounded-xl py-2.5 text-[14px] font-extrabold transition ${
className={`rounded-xl py-2.5 text-[15px] font-extrabold transition ${
tab === val
? "bg-[var(--share)] text-white"
: "border border-[var(--line-d)] text-[var(--ink-muted)]"
@ -74,7 +61,7 @@ export default function Leaderboard({
</div>
{/* 헤더 라벨 */}
<div className="mt-3 flex items-center justify-between px-3 text-[11px] font-bold text-[var(--ink-muted)]">
<div className="mt-3 flex items-center justify-between px-3 text-[12px] font-bold text-[var(--ink-muted)]">
<span>{tab === "ai" ? t.lbTabAI : t.lbTabVoters}</span>
<span>{t.lbPoints}</span>
</div>
@ -93,16 +80,16 @@ export default function Leaderboard({
}`}
>
<span
className={`w-6 shrink-0 text-center font-mono text-[15px] font-extrabold tabular-nums ${
className={`w-6 shrink-0 text-center font-mono text-[16px] font-extrabold tabular-nums ${
top3 ? "text-[var(--share)]" : "text-[var(--ink-muted)]"
}`}
>
{i + 1}
</span>
<span className="min-w-0 flex-1 truncate text-[15px] font-bold text-white">
<span className="min-w-0 flex-1 truncate text-[16px] font-bold text-white">
{r.label}
</span>
<span className="shrink-0 font-mono text-[15px] font-extrabold tabular-nums text-white">
<span className="shrink-0 font-mono text-[16px] font-extrabold tabular-nums text-white">
{r.points.toLocaleString()}
<span className="ml-0.5 text-[12px] text-[var(--ink-muted)]">
{t.lbUnit}
@ -119,7 +106,7 @@ export default function Leaderboard({
{!defaultOpen && (
<button
onClick={() => setOpen((v) => !v)}
className="mt-3 flex w-full items-center justify-center gap-1 rounded-xl border border-[var(--line-d)] py-2.5 text-[13px] font-bold text-[var(--ink-muted)] transition active:scale-[0.99]"
className="mt-3 flex w-full items-center justify-center gap-1.5 rounded-xl border border-[var(--share)] bg-[rgba(166,94,255,0.12)] py-2.5 text-[15px] font-extrabold text-[var(--share)] transition active:scale-[0.99]"
>
{open ? t.lbClose : t.lbOpen}
<span aria-hidden>{open ? "▲" : "▼"}</span>

View File

@ -40,6 +40,7 @@ export function roundLabel(label: string, lang: Lang): string {
type Dict = {
heroPill: string;
back: string;
backPrev: string;
share: string;
shareCopied: string;
introLead: string;
@ -113,6 +114,7 @@ export const DICT: Record<Lang, Dict> = {
ko: {
heroPill: "AI와 함께하는 2026 글로벌 축구 축전 승부예측 챌린지",
back: "← 전체 일정",
backPrev: "← 투표로 돌아가기",
share: "투표 공유하기",
shareCopied: "링크 복사됨 ✓",
introLead: "GPT · Claude · Gemini가 매 경기를\n서로 다르게 예측합니다.",
@ -186,6 +188,7 @@ export const DICT: Record<Lang, Dict> = {
en: {
heroPill: "AI-powered Global Football Festival 2026 match predictions",
back: "← All matches",
backPrev: "← Back to voting",
share: "Share vote",
shareCopied: "Link copied ✓",
introLead: "GPT · Claude · Gemini predict\nevery match differently.",