Implement dark-first design system with Geist typography
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:
@@ -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">
|
||||
← 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} — {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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user