Implement dark-first design system with Geist typography
All checks were successful
CI / backend-lint (pull_request) Successful in 9s
CI / actions-lint (pull_request) Successful in 14s
CI / frontend-lint (pull_request) Successful in 20s

Add Tailwind v4 @theme tokens for surfaces, accents, text, borders,
and status colors. Self-host Geist Sans/Mono variable fonts. Redesign
nav with backdrop blur and active states, home page with gradient hero.
Migrate all 50+ components from ad-hoc gray/blue Tailwind classes to
semantic theme tokens (surface-*, text-*, border-*, accent-*, status-*).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 20:47:26 +01:00
parent e3b3dc5317
commit 512d1bfce5
56 changed files with 1151 additions and 1067 deletions

View File

@@ -12,7 +12,7 @@ const statusColors: Record<RunStatus, string> = {
}
const statusRing: Record<RunStatus, string> = {
completed: 'ring-blue-500',
completed: 'ring-accent-500',
active: 'ring-green-500 animate-pulse',
failed: 'ring-red-500',
}
@@ -32,18 +32,16 @@ function LegIndicator({ leg }: { leg: GenlockeLegDetail }) {
className={`w-4 h-4 rounded-full ${statusColors[status]} ring-2 ring-offset-2 ring-offset-white dark:ring-offset-gray-900 ${statusRing[status]}`}
/>
) : (
<div className="w-4 h-4 rounded-full bg-gray-300 dark:bg-gray-600" />
<div className="w-4 h-4 rounded-full bg-surface-3" />
)
const content = (
<div className="flex flex-col items-center gap-1 min-w-[80px]">
{dot}
<span className="text-xs font-medium text-gray-700 dark:text-gray-300 text-center leading-tight">
<span className="text-xs font-medium text-text-secondary text-center leading-tight">
{leg.game.name}
</span>
{status && (
<span className="text-[10px] text-gray-500 dark:text-gray-400 capitalize">{status}</span>
)}
{status && <span className="text-[10px] text-text-tertiary capitalize">{status}</span>}
</div>
)
@@ -72,7 +70,7 @@ function PokemonSprite({ pokemon }: { pokemon: RetiredPokemon }) {
}
return (
<div
className="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-600 flex items-center justify-center text-sm font-bold"
className="w-10 h-10 rounded-full bg-surface-3 flex items-center justify-center text-sm font-bold"
title={pokemon.name}
>
{pokemon.name[0]?.toUpperCase()}
@@ -137,7 +135,7 @@ export function GenlockeDetail() {
if (error || !genlocke) {
return (
<div className="max-w-4xl mx-auto p-8">
<div className="rounded-lg bg-red-50 dark:bg-red-900/20 p-4 text-red-700 dark:text-red-400">
<div className="rounded-lg bg-status-failed-bg p-4 text-status-failed">
Failed to load genlocke. Please try again.
</div>
</div>
@@ -157,11 +155,11 @@ export function GenlockeDetail() {
<div className="max-w-4xl mx-auto p-8 space-y-8">
{/* Header */}
<div>
<Link to="/genlockes" className="text-sm text-blue-600 dark:text-blue-400 hover:underline">
<Link to="/genlockes" className="text-sm text-text-link hover:underline">
&larr; Back to Genlockes
</Link>
<div className="flex items-center gap-3 mt-2">
<h1 className="text-3xl font-bold text-gray-900 dark:text-gray-100">{genlocke.name}</h1>
<h1 className="text-3xl font-bold text-text-primary">{genlocke.name}</h1>
<span
className={`px-2.5 py-0.5 rounded-full text-xs font-medium capitalize ${statusStyles[genlocke.status]}`}
>
@@ -172,8 +170,8 @@ export function GenlockeDetail() {
{/* Progress Timeline */}
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">Progress</h2>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
<h2 className="text-lg font-semibold text-text-primary mb-4">Progress</h2>
<div className="bg-surface-1 rounded-lg shadow p-6">
<div className="flex items-start gap-2 overflow-x-auto pb-2">
{genlocke.legs.map((leg, i) => (
<div key={leg.id} className="flex items-center">
@@ -181,7 +179,7 @@ export function GenlockeDetail() {
{i < genlocke.legs.length - 1 && (
<div
className={`h-0.5 w-6 mx-1 mt-[-16px] ${
leg.runStatus === 'completed' ? 'bg-blue-500' : 'bg-gray-300 dark:bg-gray-600'
leg.runStatus === 'completed' ? 'bg-blue-500' : 'bg-surface-3'
}`}
/>
)}
@@ -193,9 +191,7 @@ export function GenlockeDetail() {
{/* Cumulative Stats */}
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Cumulative Stats
</h2>
<h2 className="text-lg font-semibold text-text-primary mb-4">Cumulative Stats</h2>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
<StatCard label="Encounters" value={genlocke.stats.totalEncounters} color="blue" />
<StatCard label="Deaths" value={genlocke.stats.totalDeaths} color="red" />
@@ -211,30 +207,24 @@ export function GenlockeDetail() {
{/* Configuration */}
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Configuration
</h2>
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 space-y-4">
<h2 className="text-lg font-semibold text-text-primary mb-4">Configuration</h2>
<div className="bg-surface-1 rounded-lg shadow p-6 space-y-4">
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Genlocke Rules
</h3>
<h3 className="text-sm font-medium text-text-tertiary mb-2">Genlocke Rules</h3>
<div className="flex flex-wrap gap-1.5">
{genlocke.genlockeRules.retireHoF ? (
<span className="px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 dark:bg-purple-900/40 dark:text-purple-300">
Retire HoF Teams
</span>
) : (
<span className="text-sm text-gray-500 dark:text-gray-400">
<span className="text-sm text-text-tertiary">
No genlocke-specific rules enabled
</span>
)}
</div>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
Nuzlocke Rules
</h3>
<h3 className="text-sm font-medium text-text-tertiary mb-2">Nuzlocke Rules</h3>
<RuleBadges rules={genlocke.nuzlockeRules} />
</div>
</div>
@@ -243,13 +233,11 @@ export function GenlockeDetail() {
{/* Retired Families */}
{genlocke.genlockeRules.retireHoF && retiredByLeg.length > 0 && (
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Retired Families
</h2>
<h2 className="text-lg font-semibold text-text-primary mb-4">Retired Families</h2>
<div className="space-y-3">
{retiredByLeg.map((leg) => (
<div key={leg.legOrder} className="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">
<div key={leg.legOrder} className="bg-surface-1 rounded-lg shadow p-4">
<h3 className="text-sm font-medium text-text-tertiary mb-2">
Leg {leg.legOrder} &mdash; {leg.gameName}
</h3>
<div className="flex flex-wrap gap-1">
@@ -267,9 +255,7 @@ export function GenlockeDetail() {
{/* Quick Actions */}
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Quick Actions
</h2>
<h2 className="text-lg font-semibold text-text-primary mb-4">Quick Actions</h2>
<div className="flex flex-wrap gap-3">
{activeLeg && (
<Link
@@ -284,7 +270,7 @@ export function GenlockeDetail() {
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
showGraveyard
? 'bg-red-600 text-white hover:bg-red-700'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
: 'bg-surface-3 text-text-secondary hover:bg-surface-4'
}`}
>
Graveyard
@@ -293,8 +279,8 @@ export function GenlockeDetail() {
onClick={() => setShowLineage((v) => !v)}
className={`px-4 py-2 rounded-lg font-medium transition-colors ${
showLineage
? 'bg-blue-600 text-white hover:bg-blue-700'
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600'
? 'bg-accent-600 text-white hover:bg-accent-500'
: 'bg-surface-3 text-text-secondary hover:bg-surface-4'
}`}
>
Lineage
@@ -305,9 +291,7 @@ export function GenlockeDetail() {
{/* Graveyard */}
{showGraveyard && (
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Cumulative Graveyard
</h2>
<h2 className="text-lg font-semibold text-text-primary mb-4">Cumulative Graveyard</h2>
<GenlockeGraveyard genlockeId={id} />
</section>
)}
@@ -315,9 +299,7 @@ export function GenlockeDetail() {
{/* Lineage */}
{showLineage && (
<section>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-4">
Pokemon Lineages
</h2>
<h2 className="text-lg font-semibold text-text-primary mb-4">Pokemon Lineages</h2>
<GenlockeLineage genlockeId={id} />
</section>
)}