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:
@@ -92,17 +92,14 @@ export function ShinyEncounterModal({
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="fixed inset-0 bg-black/50" onClick={onClose} />
|
||||
<div className="relative bg-white dark:bg-gray-800 rounded-xl shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="sticky top-0 bg-white dark:bg-gray-800 border-b border-yellow-300 dark:border-yellow-600 px-6 py-4 rounded-t-xl">
|
||||
<div className="relative bg-surface-1 rounded-xl shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto">
|
||||
<div className="sticky top-0 bg-surface-1 border-b border-yellow-300 dark:border-yellow-600 px-6 py-4 rounded-t-xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2">
|
||||
<h2 className="text-lg font-semibold text-text-primary flex items-center gap-2">
|
||||
<span className="text-yellow-500">✦</span>
|
||||
Log Shiny Encounter
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||
>
|
||||
<button onClick={onClose} className="text-gray-400 hover:text-text-primary">
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -121,13 +118,11 @@ export function ShinyEncounterModal({
|
||||
<div className="px-6 py-4 space-y-4">
|
||||
{/* Route selector */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Route
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-text-secondary mb-1">Route</label>
|
||||
<select
|
||||
value={selectedRouteId ?? ''}
|
||||
onChange={(e) => handleRouteChange(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
className="w-full px-3 py-2 rounded-lg border border-border-default bg-surface-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
>
|
||||
<option value="">Select a route...</option>
|
||||
{leafRoutes.map((r) => (
|
||||
@@ -141,9 +136,7 @@ export function ShinyEncounterModal({
|
||||
{/* Pokemon Selection */}
|
||||
{selectedRouteId && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Pokemon
|
||||
</label>
|
||||
<label className="block text-sm font-medium text-text-secondary mb-1">Pokemon</label>
|
||||
{loadingPokemon ? (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<div className="w-6 h-6 border-2 border-yellow-500 border-t-transparent rounded-full animate-spin" />
|
||||
@@ -156,17 +149,15 @@ export function ShinyEncounterModal({
|
||||
placeholder="Search pokemon..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full px-3 py-1.5 mb-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
className="w-full px-3 py-1.5 mb-2 rounded-lg border border-border-default bg-surface-2 text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
/>
|
||||
)}
|
||||
<div className="max-h-64 overflow-y-auto space-y-3">
|
||||
{groupedPokemon.map(({ method, pokemon }, groupIdx) => (
|
||||
<div key={method}>
|
||||
{groupIdx > 0 && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 mb-3" />
|
||||
)}
|
||||
{groupIdx > 0 && <div className="border-t border-border-default mb-3" />}
|
||||
{hasMultipleGroups && (
|
||||
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1.5">
|
||||
<div className="text-xs font-medium text-text-tertiary mb-1.5">
|
||||
{getMethodLabel(method)}
|
||||
</div>
|
||||
)}
|
||||
@@ -179,7 +170,7 @@ export function ShinyEncounterModal({
|
||||
className={`flex flex-col items-center p-2 rounded-lg border text-center transition-colors ${
|
||||
selectedPokemon?.id === rp.id
|
||||
? 'border-yellow-500 bg-yellow-50 dark:bg-yellow-900/30'
|
||||
: 'border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600'
|
||||
: 'border-border-default hover:border-border-default'
|
||||
}`}
|
||||
>
|
||||
{rp.pokemon.spriteUrl ? (
|
||||
@@ -189,11 +180,11 @@ export function ShinyEncounterModal({
|
||||
className="w-10 h-10"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-600 flex items-center justify-center text-xs font-bold">
|
||||
<div className="w-10 h-10 rounded-full bg-surface-3 flex items-center justify-center text-xs font-bold">
|
||||
{rp.pokemon.name[0]?.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
<span className="text-xs text-gray-700 dark:text-gray-300 mt-1 capitalize">
|
||||
<span className="text-xs text-text-secondary mt-1 capitalize">
|
||||
{rp.pokemon.name}
|
||||
</span>
|
||||
{SPECIAL_METHODS.includes(rp.encounterMethod) && (
|
||||
@@ -211,9 +202,7 @@ export function ShinyEncounterModal({
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 py-2">
|
||||
No pokemon data for this route
|
||||
</p>
|
||||
<p className="text-sm text-text-tertiary py-2">No pokemon data for this route</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -223,7 +212,7 @@ export function ShinyEncounterModal({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="shiny-nickname"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
className="block text-sm font-medium text-text-secondary mb-1"
|
||||
>
|
||||
Nickname
|
||||
</label>
|
||||
@@ -233,7 +222,7 @@ export function ShinyEncounterModal({
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
placeholder="Give it a name..."
|
||||
className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
className="w-full px-3 py-2 rounded-lg border border-border-default bg-surface-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -243,7 +232,7 @@ export function ShinyEncounterModal({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="shiny-catch-level"
|
||||
className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1"
|
||||
className="block text-sm font-medium text-text-secondary mb-1"
|
||||
>
|
||||
Catch Level
|
||||
</label>
|
||||
@@ -259,17 +248,17 @@ export function ShinyEncounterModal({
|
||||
? `${selectedPokemon.minLevel}–${selectedPokemon.maxLevel}`
|
||||
: 'Level'
|
||||
}
|
||||
className="w-24 px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
className="w-24 px-3 py-2 rounded-lg border border-border-default bg-surface-2 text-text-primary focus:outline-none focus:ring-2 focus:ring-yellow-500"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="sticky bottom-0 bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 px-6 py-4 rounded-b-xl flex justify-end gap-3">
|
||||
<div className="sticky bottom-0 bg-surface-1 border-t border-border-default px-6 py-4 rounded-b-xl flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors"
|
||||
className="px-4 py-2 text-text-secondary bg-surface-2 rounded-lg font-medium hover:bg-surface-3 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user