From 99d1ba1bcd04ec4f0f745c84b05ee8dab3577a19 Mon Sep 17 00:00:00 2001 From: Lukas Date: Tue, 10 Mar 2026 19:50:22 +0100 Subject: [PATCH] Implement the 028-semantic-hover-tokens feature that unifies hover colors across all interactive UI components via six CSS custom property tokens (three text, three background) defined in the Tailwind v4 theme, replacing hardcoded hover classes in 9 component files plus the shared Button primitive with semantic token references so all hover colors can be globally reconfigured from one place Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 1 + apps/web/src/components/ac-shield.tsx | 2 +- apps/web/src/components/action-bar.tsx | 2 +- apps/web/src/components/bestiary-search.tsx | 4 +- apps/web/src/components/combatant-row.tsx | 16 +-- apps/web/src/components/condition-picker.tsx | 2 +- apps/web/src/components/condition-tags.tsx | 4 +- apps/web/src/components/stat-block-panel.tsx | 4 +- apps/web/src/components/turn-navigation.tsx | 8 +- apps/web/src/components/ui/button.tsx | 4 +- apps/web/src/index.css | 6 + .../checklists/requirements.md | 35 +++++ specs/028-semantic-hover-tokens/data-model.md | 59 ++++++++ specs/028-semantic-hover-tokens/plan.md | 70 ++++++++++ specs/028-semantic-hover-tokens/quickstart.md | 37 +++++ specs/028-semantic-hover-tokens/research.md | 83 +++++++++++ specs/028-semantic-hover-tokens/spec.md | 96 +++++++++++++ specs/028-semantic-hover-tokens/tasks.md | 132 ++++++++++++++++++ 18 files changed, 542 insertions(+), 23 deletions(-) create mode 100644 specs/028-semantic-hover-tokens/checklists/requirements.md create mode 100644 specs/028-semantic-hover-tokens/data-model.md create mode 100644 specs/028-semantic-hover-tokens/plan.md create mode 100644 specs/028-semantic-hover-tokens/quickstart.md create mode 100644 specs/028-semantic-hover-tokens/research.md create mode 100644 specs/028-semantic-hover-tokens/spec.md create mode 100644 specs/028-semantic-hover-tokens/tasks.md diff --git a/CLAUDE.md b/CLAUDE.md index 9bd6581..bc95b44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -80,6 +80,7 @@ The constitution (`.specify/memory/constitution.md`) governs all feature work: - TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4, Lucide React (icons), Vite 6 (026-roll-initiative) - N/A (no storage changes — existing localStorage persistence handles initiative via `setInitiativeUseCase`) (026-roll-initiative) - N/A (no storage changes — purely presentational) (027-ui-polish) +- TypeScript 5.8 (strict mode, verbatimModuleSyntax) + React 19, Tailwind CSS v4, Vite 6 + Tailwind CSS v4 (CSS-native `@theme` theming), Lucide React (icons) (028-semantic-hover-tokens) ## Recent Changes - 003-remove-combatant: Added TypeScript 5.x (strict mode, verbatimModuleSyntax) + React 19, Vite diff --git a/apps/web/src/components/ac-shield.tsx b/apps/web/src/components/ac-shield.tsx index a7cc913..e276c41 100644 --- a/apps/web/src/components/ac-shield.tsx +++ b/apps/web/src/components/ac-shield.tsx @@ -12,7 +12,7 @@ export function AcShield({ value, onClick, className }: AcShieldProps) { type="button" onClick={onClick} className={cn( - "relative inline-flex items-center justify-center text-sm tabular-nums text-muted-foreground transition-colors hover:text-primary", + "relative inline-flex items-center justify-center text-sm tabular-nums text-muted-foreground transition-colors hover:text-hover-neutral", className, )} style={{ width: 28, height: 32 }} diff --git a/apps/web/src/components/action-bar.tsx b/apps/web/src/components/action-bar.tsx index fcdcebf..c1c6bad 100644 --- a/apps/web/src/components/action-bar.tsx +++ b/apps/web/src/components/action-bar.tsx @@ -107,7 +107,7 @@ export function ActionBar({ className={`flex w-full items-center justify-between px-3 py-1.5 text-left text-sm ${ i === suggestionIndex ? "bg-accent/20 text-foreground" - : "text-foreground hover:bg-accent/10" + : "text-foreground hover:bg-hover-neutral-bg" }`} onClick={() => handleSelectSuggestion(creature)} onMouseEnter={() => setSuggestionIndex(i)} diff --git a/apps/web/src/components/bestiary-search.tsx b/apps/web/src/components/bestiary-search.tsx index 2d1c24d..9ceb438 100644 --- a/apps/web/src/components/bestiary-search.tsx +++ b/apps/web/src/components/bestiary-search.tsx @@ -87,7 +87,7 @@ export function BestiarySearch({ @@ -108,7 +108,7 @@ export function BestiarySearch({ className={`flex w-full items-center justify-between px-3 py-1.5 text-left text-sm ${ i === highlightIndex ? "bg-accent/20 text-foreground" - : "text-foreground hover:bg-accent/10" + : "text-foreground hover:bg-hover-neutral-bg" }`} onClick={() => onSelectCreature(creature)} onMouseEnter={() => setHighlightIndex(i)} diff --git a/apps/web/src/components/combatant-row.tsx b/apps/web/src/components/combatant-row.tsx index 5fb2c9d..02b59eb 100644 --- a/apps/web/src/components/combatant-row.tsx +++ b/apps/web/src/components/combatant-row.tsx @@ -91,7 +91,7 @@ function EditableName({ e.stopPropagation(); startEditing(); }} - className="truncate text-left text-sm text-foreground hover:text-primary transition-colors" + className="truncate text-left text-sm text-foreground hover:text-hover-neutral transition-colors" > {name} @@ -150,7 +150,7 @@ function MaxHpDisplay({ @@ -190,7 +190,7 @@ function ClickableHp({ type="button" onClick={() => setPopoverOpen(true)} className={cn( - "inline-block h-7 min-w-[3ch] text-center text-sm font-medium leading-7 tabular-nums transition-colors hover:text-primary", + "inline-block h-7 min-w-[3ch] text-center text-sm font-medium leading-7 tabular-nums transition-colors hover:text-hover-neutral", status === "bloodied" && "text-amber-400", status === "unconscious" && "text-red-400", status === "healthy" && "text-foreground", @@ -324,7 +324,7 @@ function InitiativeDisplay({ type="button" onClick={() => onRollInitiative(combatantId)} className={cn( - "flex h-7 w-full items-center justify-center text-muted-foreground transition-colors hover:text-primary", + "flex h-7 w-full items-center justify-center text-muted-foreground transition-colors hover:text-hover-neutral", dimmed && "opacity-50", )} title="Roll initiative" @@ -344,8 +344,8 @@ function InitiativeDisplay({ className={cn( "h-7 w-full text-center text-sm leading-7 tabular-nums transition-colors", initiative !== undefined - ? "font-medium text-foreground hover:text-primary" - : "text-muted-foreground hover:text-primary", + ? "font-medium text-foreground hover:text-hover-neutral" + : "text-muted-foreground hover:text-hover-neutral", dimmed && "opacity-50", )} > @@ -429,7 +429,7 @@ export function CombatantRow({ title="Concentrating" aria-label="Toggle concentration" className={cn( - "flex w-full items-center justify-center self-stretch -my-2 -ml-[2px] pl-[14px] transition-opacity", + "flex w-full items-center justify-center self-stretch -my-2 -ml-[2px] pl-[14px] transition-opacity hover:text-hover-neutral hover:opacity-100", combatant.isConcentrating ? dimmed ? "opacity-50 text-purple-400" @@ -519,7 +519,7 @@ export function CombatantRow({ @@ -63,7 +63,7 @@ export function StatBlockPanel({ creature, onClose }: StatBlockPanelProps) { diff --git a/apps/web/src/components/turn-navigation.tsx b/apps/web/src/components/turn-navigation.tsx index 7c4cd38..0db430f 100644 --- a/apps/web/src/components/turn-navigation.tsx +++ b/apps/web/src/components/turn-navigation.tsx @@ -27,7 +27,7 @@ export function TurnNavigation({