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:
2026-06-15 18:08:23 +02:00
parent 187ee2e312
commit b141356247
125 changed files with 279 additions and 322 deletions
+24 -27
View File
@@ -32,18 +32,17 @@ const HOME_QUERY = gql`
function SectionHeader({ label }: { label: string }) {
return (
<div className="flex items-center gap-2.5 mb-4">
<div className="w-[3px] h-[18px] bg-[#22c55e] rounded-sm" />
<span className="text-[11px] text-[#2a5c35] font-bold tracking-[0.12em] uppercase">{label}</span>
<div className="w-[3px] h-[18px] bg-green rounded-sm" />
<span className="text-[11px] text-green-muted font-bold tracking-[0.12em] uppercase">{label}</span>
</div>
)
}
function StatPill({ label, value }: { label: string; value: string | number }) {
return (
<div className="flex-1 min-w-[90px] rounded-xl p-3.5 px-5"
style={{ background: 'rgba(34,197,94,0.05)', border: '1px solid rgba(34,197,94,0.12)' }}>
<div className="text-[9px] text-[#2a5c35] tracking-[0.13em] uppercase mb-1.5 whitespace-nowrap">{label}</div>
<div className="font-['Bebas_Neue'] text-[30px] text-[#22c55e] leading-none">{value ?? ''}</div>
<div className="flex-1 min-w-[90px] rounded-xl p-3.5 px-5 bg-green/5 border border-green/[12%]">
<div className="text-[9px] text-green-muted tracking-[0.13em] uppercase mb-1.5 whitespace-nowrap">{label}</div>
<div className="font-['Bebas_Neue'] text-[30px] text-green leading-none">{value ?? ''}</div>
</div>
)
}
@@ -58,13 +57,13 @@ function UpcomingFixture({ match }: { match: UpcomingMatch }) {
const time = match.time?.split(' ')[0] ?? ''
return (
<Link href={`/tournaments/${match.year}#match-${match.id}`}>
<div className="glass-card rounded-[10px] p-3 px-4 flex items-center gap-2.5 hover:border-[rgba(34,197,94,0.2)] transition-colors cursor-pointer">
<div className="glass-card rounded-[10px] p-3 px-4 flex items-center gap-2.5 hover:border-green/20 transition-colors cursor-pointer">
<TeamFlag name={match.team1.name} iso2={match.team1.iso2} size="sm" />
<div className="flex-1 text-[13px] text-[#6abf7a] font-medium truncate">
{match.team1.name} <span className="text-[#2a5c35]">vs</span> {match.team2.name}
<div className="flex-1 text-[13px] text-green-sec font-medium truncate">
{match.team1.name} <span className="text-green-muted">vs</span> {match.team2.name}
</div>
<TeamFlag name={match.team2.name} iso2={match.team2.iso2} size="sm" />
{time && <div className="text-[11px] text-[#2a5c35] whitespace-nowrap ml-1">{time}</div>}
{time && <div className="text-[11px] text-green-muted whitespace-nowrap ml-1">{time}</div>}
</div>
</Link>
)
@@ -100,9 +99,8 @@ export default function HomePage() {
return (
<div>
{/* ── Hero ── */}
<div className="pitch-grid border-b" style={{
<div className="pitch-grid border-b border-border" style={{
background: 'linear-gradient(145deg,rgba(10,26,14,0.9) 0%,rgba(13,36,22,0.9) 55%,rgba(10,26,14,0.9) 100%)',
borderColor: 'rgba(34,197,94,0.15)',
padding: '52px 0 44px',
}}>
<div className="max-w-[1200px] mx-auto px-7">
@@ -110,15 +108,15 @@ export default function HomePage() {
{live.length > 0
? <LiveBadge label="Live · Group Stage in Progress" />
: <div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-[#22c55e] inline-block" />
<span className="text-[11px] font-bold text-[#22c55e] tracking-[0.14em] uppercase">World Cup 2026 · In Progress</span>
<span className="w-2 h-2 rounded-full bg-green inline-block" />
<span className="text-[11px] font-bold text-green tracking-[0.14em] uppercase">World Cup 2026 · In Progress</span>
</div>
}
</div>
<h1 className="font-['Bebas_Neue'] text-[clamp(50px,9vw,100px)] tracking-[0.04em] text-white leading-[0.92] mb-2.5">
World Cup 2026
</h1>
<p className="text-[#2a5c35] text-sm mb-9">
<p className="text-green-muted text-sm mb-9">
USA · Canada · Mexico &nbsp;·&nbsp; 11 June 19 July 2026 · 48 Teams
</p>
<div className="flex gap-2.5 flex-wrap max-w-[760px]">
@@ -132,7 +130,7 @@ export default function HomePage() {
<StatPill label="2026 Avg" value={wc2026.avgGoalsPerGame ? Number(wc2026.avgGoalsPerGame).toFixed(2) : ''} />
</>}
</> : [1,2,3,4].map(i => (
<div key={i} className="flex-1 min-w-[90px] h-20 rounded-xl animate-pulse" style={{ background: 'rgba(34,197,94,0.04)' }} />
<div key={i} className="flex-1 min-w-[90px] h-20 rounded-xl animate-pulse bg-green/[4%]" />
))}
</div>
</div>
@@ -184,30 +182,29 @@ export default function HomePage() {
<div className="glass-card">
{scorers.map((s, i) => (
<Link key={s.playerName} href={`/players/${encodeURIComponent(s.playerName)}`}>
<div className="flex items-center gap-2 sm:gap-3 px-3 sm:px-4 py-3 border-b hover:bg-[rgba(34,197,94,0.03)] transition-colors cursor-pointer"
style={{ borderColor: 'rgba(34,197,94,0.06)', background: i === 0 ? 'rgba(34,197,94,0.04)' : undefined }}>
<span className="text-[11px] text-[#2a5c35] w-5 text-right font-bold flex-shrink-0">{i + 1}</span>
<div className={`flex items-center gap-2 sm:gap-3 px-3 sm:px-4 py-3 border-b border-green/[6%] hover:bg-green/[3%] transition-colors cursor-pointer ${i === 0 ? 'bg-green/[4%]' : ''}`}>
<span className="text-[11px] text-green-muted w-5 text-right font-bold flex-shrink-0">{i + 1}</span>
{s.team && <TeamFlag name={s.team.name} iso2={s.team.iso2} size="sm" />}
<div className="flex-1 min-w-0">
<div className={`text-sm font-semibold truncate ${i === 0 ? 'text-[#dff5e8]' : 'text-[#6abf7a]'}`}>{s.playerName}</div>
<div className="text-[10px] text-[#2a5c35] truncate">{s.team?.name}{s.penalties > 0 ? ` · ${s.penalties}P` : ''}</div>
<div className={`text-sm font-semibold truncate ${i === 0 ? 'text-text' : 'text-green-sec'}`}>{s.playerName}</div>
<div className="text-[10px] text-green-muted truncate">{s.team?.name}{s.penalties > 0 ? ` · ${s.penalties}P` : ''}</div>
</div>
<div className="hidden sm:block w-24 h-1 rounded-full overflow-hidden flex-shrink-0" style={{ background: 'rgba(34,197,94,0.1)' }}>
<div className="h-full rounded-full bg-[#22c55e] transition-all" style={{ width: `${(s.goals / maxGoals) * 100}%` }} />
<div className="hidden sm:block w-24 h-1 rounded-full overflow-hidden flex-shrink-0 bg-green/10">
<div className="h-full rounded-full bg-green transition-all" style={{ width: `${(s.goals / maxGoals) * 100}%` }} />
</div>
<span className="font-['Bebas_Neue'] text-[22px] text-[#22c55e] min-w-[24px] text-right flex-shrink-0">{s.goals}</span>
<span className="font-['Bebas_Neue'] text-[22px] text-green min-w-[24px] text-right flex-shrink-0">{s.goals}</span>
</div>
</Link>
))}
</div>
<p className="text-[10px] text-[#1a3a22] mt-3 text-center">
<Link href="/stats" className="hover:text-[#2a5c35]">View all-time top scorers </Link>
<p className="text-[10px] text-green-dark mt-3 text-center">
<Link href="/stats" className="hover:text-green-muted">View all-time top scorers </Link>
</p>
</div>
)}
{loading && !data && (
<div className="py-16 text-center text-[#2a5c35] text-sm">Loading live World Cup data</div>
<div className="py-16 text-center text-green-muted text-sm">Loading live World Cup data</div>
)}
</div>
</div>