refactor: replace hardcoded hex colors with theme tokens, move data/ to root
- Add --color-green-mid token (#4a7a55) to @theme for dimmer stat values
- Replace all text-[#hex]/bg-[#hex] arbitrary values with named tokens:
text-green, text-green-light, text-green-sec, text-green-muted,
text-green-dark, text-green-mid, text-text, bg-card, bg-bg, border-border
- Replace rgba(34,197,94,X) inline styles with bg-green/X opacity modifiers
- Convert single-prop style={{ borderColor/background }} to className
- Fix SVG stroke="#dff5e8" → stroke="currentColor"
- Use CSS variables in globals.css base styles (background-color, color)
- Move app/data/wikipedia/ → data/ (project root, not inside Next.js app dir)
- Update Dockerfile, seed.ts, scrape-wikipedia.ts paths accordingly
- Remove unused app/data/world_cup.csv
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+29
-30
@@ -62,7 +62,7 @@ function SearchContent() {
|
||||
|
||||
return (
|
||||
<div className="max-w-[1200px] mx-auto px-7 py-10 pb-16">
|
||||
<h1 className="font-['Bebas_Neue'] text-[52px] tracking-[0.04em] text-[#22c55e] leading-none mb-6">Search</h1>
|
||||
<h1 className="font-['Bebas_Neue'] text-[52px] tracking-[0.04em] text-green leading-none mb-6">Search</h1>
|
||||
|
||||
{/* Search input */}
|
||||
<div className="relative max-w-lg mb-8">
|
||||
@@ -70,47 +70,46 @@ function SearchContent() {
|
||||
type="text" value={q} onChange={e => setQ(e.target.value)}
|
||||
placeholder="Search teams, players, tournaments…"
|
||||
autoFocus
|
||||
className="w-full pl-10 pr-4 py-3 rounded-2xl text-[#dff5e8] text-sm outline-none"
|
||||
style={{ background: 'rgba(34,197,94,0.06)', border: '1px solid rgba(34,197,94,0.2)' }}
|
||||
className="w-full pl-10 pr-4 py-3 rounded-2xl text-text text-sm outline-none bg-green/[6%] border-green/20"
|
||||
/>
|
||||
<svg className="absolute left-3.5 top-1/2 -translate-y-1/2 opacity-40" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#dff5e8" strokeWidth="2.5">
|
||||
<svg className="absolute left-3.5 top-1/2 -translate-y-1/2 opacity-40" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||||
</svg>
|
||||
{loading && <div className="absolute right-3.5 top-1/2 -translate-y-1/2 w-4 h-4 border-2 border-[#22c55e] border-t-transparent rounded-full animate-spin" />}
|
||||
{loading && <div className="absolute right-3.5 top-1/2 -translate-y-1/2 w-4 h-4 border-2 border-green border-t-transparent rounded-full animate-spin" />}
|
||||
</div>
|
||||
|
||||
{/* Prompt */}
|
||||
{skip && (
|
||||
<div className="flex flex-col items-center py-20 text-center">
|
||||
<div className="text-[56px] mb-5">🔍</div>
|
||||
<div className="text-[#2a5c35] text-base">Search for nations, players, or tournaments…</div>
|
||||
<div className="text-[#1a3a22] text-sm mt-2">Examples: "Brazil", "Ronaldo", "1966"</div>
|
||||
<div className="text-green-muted text-base">Search for nations, players, or tournaments…</div>
|
||||
<div className="text-green-dark text-sm mt-2">Examples: "Brazil", "Ronaldo", "1966"</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* No results */}
|
||||
{!skip && !loading && total === 0 && (
|
||||
<div className="text-center text-[#1a3a22] py-16 text-sm">No results for "{debouncedQ}"</div>
|
||||
<div className="text-center text-green-dark py-16 text-sm">No results for "{debouncedQ}"</div>
|
||||
)}
|
||||
|
||||
{/* Results count */}
|
||||
{!skip && total > 0 && (
|
||||
<div className="text-[13px] text-[#2a5c35] mb-6">{total} result{total !== 1 ? 's' : ''} for "{debouncedQ}"</div>
|
||||
<div className="text-[13px] text-green-muted mb-6">{total} result{total !== 1 ? 's' : ''} for "{debouncedQ}"</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-6">
|
||||
{/* Teams */}
|
||||
{results?.teams?.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.12em] uppercase mb-3">Teams</h3>
|
||||
<h3 className="text-[11px] text-green-muted font-bold tracking-[0.12em] uppercase mb-3">Teams</h3>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-2.5">
|
||||
{results.teams.map((t: { name: string; iso2?: string | null; slug: string; stats?: { appearances: number; titles: number } | null }) => (
|
||||
<Link key={t.name} href={`/teams/${t.slug}`}>
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-[rgba(34,197,94,0.25)] transition-colors cursor-pointer">
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-green/25 transition-colors cursor-pointer">
|
||||
<TeamFlag name={t.name} iso2={t.iso2} size="md" />
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-[#dff5e8]">{t.name}</div>
|
||||
<div className="text-[10px] text-[#2a5c35]">
|
||||
<div className="text-sm font-semibold text-text">{t.name}</div>
|
||||
<div className="text-[10px] text-green-muted">
|
||||
{t.stats?.appearances ?? 0} WCs{t.stats?.titles ? <span className="inline-flex items-center gap-0.5 ml-1">· {t.stats.titles}<TrophyIcon className="w-3 h-3 inline" /></span> : ''}
|
||||
</div>
|
||||
</div>
|
||||
@@ -124,17 +123,17 @@ function SearchContent() {
|
||||
{/* Players */}
|
||||
{results?.players?.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.12em] uppercase mb-3">Players</h3>
|
||||
<h3 className="text-[11px] text-green-muted font-bold tracking-[0.12em] uppercase mb-3">Players</h3>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(220px,1fr))] gap-2.5">
|
||||
{results.players.map((p: { playerName: string; goals: number; tournaments: number; team?: { name: string; iso2?: string | null } | null }) => (
|
||||
<Link key={p.playerName} href={`/players/${encodeURIComponent(p.playerName)}`}>
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-[rgba(34,197,94,0.25)] transition-colors cursor-pointer">
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-green/25 transition-colors cursor-pointer">
|
||||
{p.team && <TeamFlag name={p.team.name} iso2={p.team.iso2} size="sm" />}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-semibold text-[#dff5e8] truncate">{p.playerName}</div>
|
||||
<div className="text-[10px] text-[#2a5c35]">{p.team?.name} · {p.tournaments} WC{p.tournaments !== 1 ? 's' : ''}</div>
|
||||
<div className="text-sm font-semibold text-text truncate">{p.playerName}</div>
|
||||
<div className="text-[10px] text-green-muted">{p.team?.name} · {p.tournaments} WC{p.tournaments !== 1 ? 's' : ''}</div>
|
||||
</div>
|
||||
<span className="font-['Bebas_Neue'] text-xl text-[#22c55e] flex-shrink-0 inline-flex items-center gap-0.5">{p.goals}<FireIcon className="w-3.5 h-3.5" /></span>
|
||||
<span className="font-['Bebas_Neue'] text-xl text-green flex-shrink-0 inline-flex items-center gap-0.5">{p.goals}<FireIcon className="w-3.5 h-3.5" /></span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
@@ -145,15 +144,15 @@ function SearchContent() {
|
||||
{/* Tournaments */}
|
||||
{results?.tournaments?.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.12em] uppercase mb-3">Tournaments</h3>
|
||||
<h3 className="text-[11px] text-green-muted font-bold tracking-[0.12em] uppercase mb-3">Tournaments</h3>
|
||||
<div className="grid grid-cols-[repeat(auto-fill,minmax(180px,1fr))] gap-2.5">
|
||||
{results.tournaments.map((t: { year: number; host: string; winner?: string | null; totalGoals?: number | null; matchesCount?: number | null }) => (
|
||||
<Link key={t.year} href={`/tournaments/${t.year}`}>
|
||||
<div className="glass-card p-4 rounded-xl hover:border-[rgba(34,197,94,0.25)] transition-colors cursor-pointer">
|
||||
<div className="font-['Bebas_Neue'] text-3xl text-[#22c55e]">{t.year}</div>
|
||||
<div className="text-sm text-[#dff5e8]">{t.host}</div>
|
||||
{t.winner && <div className="text-[10px] text-[#2a5c35] mt-1 flex items-center gap-1"><TrophyIcon className="w-3 h-3 flex-shrink-0" />{t.winner}</div>}
|
||||
{t.totalGoals && <div className="text-[10px] text-[#1a3a22] flex items-center gap-1"><FireIcon className="w-3 h-3 flex-shrink-0" />{t.totalGoals} goals</div>}
|
||||
<div className="glass-card p-4 rounded-xl hover:border-green/25 transition-colors cursor-pointer">
|
||||
<div className="font-['Bebas_Neue'] text-3xl text-green">{t.year}</div>
|
||||
<div className="text-sm text-text">{t.host}</div>
|
||||
{t.winner && <div className="text-[10px] text-green-muted mt-1 flex items-center gap-1"><TrophyIcon className="w-3 h-3 flex-shrink-0" />{t.winner}</div>}
|
||||
{t.totalGoals && <div className="text-[10px] text-green-dark flex items-center gap-1"><FireIcon className="w-3 h-3 flex-shrink-0" />{t.totalGoals} goals</div>}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
@@ -164,16 +163,16 @@ function SearchContent() {
|
||||
{/* Matches */}
|
||||
{results?.matches?.length > 0 && (
|
||||
<section>
|
||||
<h3 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.12em] uppercase mb-3">Matches</h3>
|
||||
<h3 className="text-[11px] text-green-muted font-bold tracking-[0.12em] uppercase mb-3">Matches</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{results.matches.map((m: SearchMatch) => (
|
||||
<Link key={m.id} href={`/tournaments/${m.year}#match-${m.id}`}>
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-[rgba(34,197,94,0.25)] transition-colors cursor-pointer">
|
||||
<div className="glass-card flex items-center gap-3 p-3 px-4 rounded-xl hover:border-green/25 transition-colors cursor-pointer">
|
||||
<TeamFlag name={m.team1.name} iso2={m.team1.iso2} size="sm" />
|
||||
<div className="flex-1 text-sm text-[#dff5e8]">{m.team1.name} vs {m.team2.name}</div>
|
||||
{m.scoreFt && <span className="font-['Bebas_Neue'] text-lg text-[#22c55e]">{m.scoreFt[0]}–{m.scoreFt[1]}</span>}
|
||||
<div className="flex-1 text-sm text-text">{m.team1.name} vs {m.team2.name}</div>
|
||||
{m.scoreFt && <span className="font-['Bebas_Neue'] text-lg text-green">{m.scoreFt[0]}–{m.scoreFt[1]}</span>}
|
||||
<TeamFlag name={m.team2.name} iso2={m.team2.iso2} size="sm" />
|
||||
<div className="text-[10px] text-[#2a5c35] whitespace-nowrap">{m.year} · {m.round}</div>
|
||||
<div className="text-[10px] text-green-muted whitespace-nowrap">{m.year} · {m.round}</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
@@ -187,7 +186,7 @@ function SearchContent() {
|
||||
|
||||
export default function SearchPage() {
|
||||
return (
|
||||
<Suspense fallback={<div className="p-10 text-[#2a5c35]">Loading…</div>}>
|
||||
<Suspense fallback={<div className="p-10 text-green-muted">Loading…</div>}>
|
||||
<SearchContent />
|
||||
</Suspense>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user