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:
+35
-37
@@ -77,11 +77,11 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
const years = Object.keys(matchesByYear).map(Number).sort((a, b) => b - a)
|
||||
|
||||
if (loading && !teamData) {
|
||||
return <div className="max-w-[1200px] mx-auto px-7 py-10 text-[#2a5c35]">Loading team…</div>
|
||||
return <div className="max-w-[1200px] mx-auto px-7 py-10 text-green-muted">Loading team…</div>
|
||||
}
|
||||
|
||||
if (!team) {
|
||||
return <div className="max-w-[1200px] mx-auto px-7 py-10 text-[#2a5c35]">Team not found.</div>
|
||||
return <div className="max-w-[1200px] mx-auto px-7 py-10 text-green-muted">Team not found.</div>
|
||||
}
|
||||
|
||||
const s = team.stats
|
||||
@@ -95,13 +95,13 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
<div className="flex items-center gap-6 flex-wrap">
|
||||
<TeamFlag name={team.name} iso2={team.iso2} size="xl" />
|
||||
<div>
|
||||
<h1 className="font-['Bebas_Neue'] text-[56px] text-[#22c55e] leading-none">{team.name}</h1>
|
||||
<h1 className="font-['Bebas_Neue'] text-[56px] text-green leading-none">{team.name}</h1>
|
||||
<div className="flex gap-3 mt-2 flex-wrap">
|
||||
{team.fifaCode && <span className="text-[11px] text-[#2a5c35] font-bold tracking-wider">{team.fifaCode}</span>}
|
||||
{team.confederation && <span className="text-[11px] text-[#2a5c35]">{team.confederation}</span>}
|
||||
{team.continent && <span className="text-[11px] text-[#2a5c35]">{team.continent}</span>}
|
||||
{team.fifaCode && <span className="text-[11px] text-green-muted font-bold tracking-wider">{team.fifaCode}</span>}
|
||||
{team.confederation && <span className="text-[11px] text-green-muted">{team.confederation}</span>}
|
||||
{team.continent && <span className="text-[11px] text-green-muted">{team.continent}</span>}
|
||||
{(s?.titles ?? 0) > 0 && (
|
||||
<span className="inline-flex items-center gap-1 text-[11px] text-[#22c55e] font-bold">
|
||||
<span className="inline-flex items-center gap-1 text-[11px] text-green font-bold">
|
||||
{Array.from({ length: s?.titles ?? 0 }).map((_, i) => <TrophyIcon key={i} className="w-3.5 h-3.5" />)}
|
||||
{s?.titles} title{(s?.titles ?? 0) !== 1 ? 's' : ''}
|
||||
</span>
|
||||
@@ -116,7 +116,7 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
{/* Stats grid */}
|
||||
{s && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.14em] uppercase mb-4">World Cup Record</h2>
|
||||
<h2 className="text-[11px] text-green-muted font-bold tracking-[0.14em] uppercase mb-4">World Cup Record</h2>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-3">
|
||||
{[
|
||||
{ label: 'Appearances', value: s.appearances },
|
||||
@@ -125,28 +125,28 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
{ label: 'Goals For', value: s.goalsFor },
|
||||
].map(item => (
|
||||
<div key={item.label} className="glass-card rounded-xl p-4">
|
||||
<div className="text-[9px] text-[#2a5c35] tracking-[0.1em] uppercase mb-1.5">{item.label}</div>
|
||||
<div className="font-['Bebas_Neue'] text-3xl text-[#22c55e]">{item.value}</div>
|
||||
<div className="text-[9px] text-green-muted tracking-[0.1em] uppercase mb-1.5">{item.label}</div>
|
||||
<div className="font-['Bebas_Neue'] text-3xl text-green">{item.value}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="glass-card rounded-xl">
|
||||
<div className="grid px-4 py-2.5 text-[9px] text-[#2a5c35] tracking-[0.1em] uppercase"
|
||||
<div className="grid px-4 py-2.5 text-[9px] text-green-muted tracking-[0.1em] uppercase"
|
||||
style={{ gridTemplateColumns: '1fr 44px 44px 44px 60px 60px 60px' }}>
|
||||
<span>Team</span><span className="text-center">W</span><span className="text-center">D</span>
|
||||
<span className="text-center">L</span><span className="text-center">GF</span>
|
||||
<span className="text-center">GA</span><span className="text-center">GD</span>
|
||||
</div>
|
||||
<div className="grid px-4 py-3 border-t items-center"
|
||||
style={{ gridTemplateColumns: '1fr 44px 44px 44px 60px 60px 60px', borderColor: 'rgba(34,197,94,0.06)' }}>
|
||||
<div className="grid px-4 py-3 border-t border-green/[6%] items-center"
|
||||
style={{ gridTemplateColumns: '1fr 44px 44px 44px 60px 60px 60px' }}>
|
||||
<div className="flex items-center gap-2">
|
||||
<TeamFlag name={team.name} iso2={team.iso2} size="sm" />
|
||||
<span className="text-sm text-[#dff5e8]">{team.name}</span>
|
||||
<span className="text-sm text-text">{team.name}</span>
|
||||
</div>
|
||||
{[s.wins, s.draws, s.losses, s.goalsFor, s.goalsAgainst].map((v, i) => (
|
||||
<span key={i} className="text-center text-sm text-[#4a7a55]">{v}</span>
|
||||
<span key={i} className="text-center text-sm text-green-mid">{v}</span>
|
||||
))}
|
||||
<span className="text-center text-sm text-[#4a7a55]">{s.goalDiff >= 0 ? `+${s.goalDiff}` : s.goalDiff}</span>
|
||||
<span className="text-center text-sm text-green-mid">{s.goalDiff >= 0 ? `+${s.goalDiff}` : s.goalDiff}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -155,11 +155,11 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
{/* Tournament participations */}
|
||||
{years.length > 0 && (
|
||||
<div className="mb-8">
|
||||
<h2 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.14em] uppercase mb-4">Tournament Participations</h2>
|
||||
<h2 className="text-[11px] text-green-muted font-bold tracking-[0.14em] uppercase mb-4">Tournament Participations</h2>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{years.map(year => (
|
||||
<Link key={year} href={`/tournaments/${year}`}
|
||||
className="font-['Bebas_Neue'] text-lg px-3 py-1 rounded-lg transition-colors text-[#6abf7a] bg-[rgba(4,18,8,0.78)] border border-[rgba(34,197,94,0.15)] hover:text-[#22c55e] hover:border-[rgba(34,197,94,0.4)] backdrop-blur-sm">
|
||||
className="font-['Bebas_Neue'] text-lg px-3 py-1 rounded-lg transition-colors text-green-sec bg-bg/[78%] border border-border hover:text-green hover:border-green/40 backdrop-blur-sm">
|
||||
{year}
|
||||
</Link>
|
||||
))}
|
||||
@@ -170,14 +170,14 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
{/* Match history by year */}
|
||||
{years.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.14em] uppercase mb-4">Match History</h2>
|
||||
<h2 className="text-[11px] text-green-muted font-bold tracking-[0.14em] uppercase mb-4">Match History</h2>
|
||||
<div className="space-y-6">
|
||||
{years.map(year => {
|
||||
const yMatches = matchesByYear[year]
|
||||
return (
|
||||
<div key={year}>
|
||||
<Link href={`/tournaments/${year}`}
|
||||
className="inline-block font-['Bebas_Neue'] text-[22px] text-[#22c55e] mb-2 hover:opacity-70 transition-opacity">
|
||||
className="inline-block font-['Bebas_Neue'] text-[22px] text-green mb-2 hover:opacity-70 transition-opacity">
|
||||
{year}
|
||||
</Link>
|
||||
<div className="glass-card rounded-xl">
|
||||
@@ -194,23 +194,22 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
const result = myScore != null && theirScore != null
|
||||
? myScore > theirScore ? 'W' : myScore < theirScore ? 'L' : 'D'
|
||||
: null
|
||||
const resultColor = result === 'W' ? 'text-[#22c55e]' : result === 'L' ? 'text-[#ef4444]' : 'text-[#6abf7a]'
|
||||
const resultColor = result === 'W' ? 'text-green' : result === 'L' ? 'text-red-500' : 'text-green-sec'
|
||||
// Display the decisive score (ET score for AET matches, FT for normal, PSO for shootouts)
|
||||
const displayScore = scoreP ? null : (scoreEt ?? ft)
|
||||
return (
|
||||
<Link key={m.id} href={`/tournaments/${m.year}#match-${m.id}`}>
|
||||
<div className="flex items-center gap-3 px-3 sm:px-4 py-2.5 border-b hover:bg-[rgba(34,197,94,0.03)] transition-colors"
|
||||
style={{ borderColor: 'rgba(34,197,94,0.06)', background: i % 2 === 0 ? undefined : 'rgba(34,197,94,0.01)' }}>
|
||||
<div className={`flex items-center gap-3 px-3 sm:px-4 py-2.5 border-b hover:bg-green/[3%] transition-colors border-green/[6%] ${i % 2 !== 0 ? 'bg-green/[1%]' : ''}`}>
|
||||
<span className={`text-[11px] font-bold w-4 flex-shrink-0 ${resultColor}`}>{result ?? '–'}</span>
|
||||
<TeamFlag name={opponent.name} iso2={opponent.iso2} size="sm" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm text-[#dff5e8] truncate">{opponent.name}</div>
|
||||
<div className="text-[10px] text-[#2a5c35]">
|
||||
<div className="text-sm text-text truncate">{opponent.name}</div>
|
||||
<div className="text-[10px] text-green-muted">
|
||||
{m.round}{m.group ? ` · ${m.group}` : ''}{m.date ? ` · ${formatDate(m.date)}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0">
|
||||
<div className="font-['Bebas_Neue'] text-lg text-[#22c55e] leading-none">
|
||||
<div className="font-['Bebas_Neue'] text-lg text-green leading-none">
|
||||
{scoreP
|
||||
? `${isHome ? scoreP[0] : scoreP[1]}–${isHome ? scoreP[1] : scoreP[0]}`
|
||||
: displayScore
|
||||
@@ -218,12 +217,12 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
: '–'}
|
||||
</div>
|
||||
{scoreP && ft && (
|
||||
<div className="text-[9px] text-[#2a5c35] leading-none">
|
||||
<div className="text-[9px] text-green-muted leading-none">
|
||||
{`${isHome ? ft[0] : ft[1]}–${isHome ? ft[1] : ft[0]}`} a.e.t.
|
||||
</div>
|
||||
)}
|
||||
{scoreEt && !scoreP && (
|
||||
<div className="text-[9px] text-[#2a5c35] leading-none">a.e.t.</div>
|
||||
<div className="text-[9px] text-green-muted leading-none">a.e.t.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -243,23 +242,22 @@ export default function TeamPage({ params }: { params: Promise<{ slug: string }>
|
||||
<div>
|
||||
{teamScorers.length > 0 && (
|
||||
<div>
|
||||
<h2 className="text-[11px] text-[#2a5c35] font-bold tracking-[0.14em] uppercase mb-4">Top Scorers</h2>
|
||||
<h2 className="text-[11px] text-green-muted font-bold tracking-[0.14em] uppercase mb-4">Top Scorers</h2>
|
||||
<div className="glass-card">
|
||||
{teamScorers.map((sc: { playerName: string; goals: number; penalties: number; tournaments: number }, i: number) => (
|
||||
<Link key={sc.playerName} href={`/players/${encodeURIComponent(sc.playerName)}`}>
|
||||
<div className="flex items-center gap-2.5 px-3.5 py-2.5 border-b hover:bg-[rgba(34,197,94,0.03)] cursor-pointer"
|
||||
style={{ borderColor: 'rgba(34,197,94,0.06)', background: i === 0 ? 'rgba(34,197,94,0.04)' : undefined }}>
|
||||
<span className="text-[10px] text-[#2a5c35] w-4 text-right font-bold flex-shrink-0">{i + 1}</span>
|
||||
<div className={`flex items-center gap-2.5 px-3.5 py-2.5 border-b hover:bg-green/[3%] cursor-pointer border-green/[6%] ${i === 0 ? 'bg-green/[4%]' : ''}`}>
|
||||
<span className="text-[10px] text-green-muted w-4 text-right font-bold flex-shrink-0">{i + 1}</span>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-[13px] font-semibold text-[#dff5e8] truncate">{sc.playerName}</div>
|
||||
<div className="text-[10px] text-[#2a5c35]">
|
||||
<div className="text-[13px] font-semibold text-text truncate">{sc.playerName}</div>
|
||||
<div className="text-[10px] text-green-muted">
|
||||
{sc.tournaments} WC{sc.tournaments !== 1 ? 's' : ''}{sc.penalties > 0 ? ` · ${sc.penalties}P` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-10 h-1 rounded-full flex-shrink-0" style={{ background: 'rgba(34,197,94,0.1)' }}>
|
||||
<div className="h-full rounded-full bg-[#22c55e]" style={{ width: `${(sc.goals / maxScorer) * 100}%` }} />
|
||||
<div className="w-10 h-1 rounded-full flex-shrink-0 bg-green/10">
|
||||
<div className="h-full rounded-full bg-green" style={{ width: `${(sc.goals / maxScorer) * 100}%` }} />
|
||||
</div>
|
||||
<span className="font-['Bebas_Neue'] text-xl text-[#22c55e] flex-shrink-0">{sc.goals}</span>
|
||||
<span className="font-['Bebas_Neue'] text-xl text-green flex-shrink-0">{sc.goals}</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user