diff --git a/src/components/insights/derived/band-tokens.ts b/src/components/insights/derived/band-tokens.ts index 0204d56f6..381ce9fa2 100644 --- a/src/components/insights/derived/band-tokens.ts +++ b/src/components/insights/derived/band-tokens.ts @@ -5,44 +5,54 @@ * exact same band colour grammar the shipped `health-score-card` already * uses, without each component redeclaring the map (concept cohesion). * - * No new tokens — these point at the existing `--dracula-*` variables in - * `globals.css`. A green/yellow/red band is the one semantic the whole - * v1.10 score language extends. + * These point at the *semantic* feedback tokens (`--success` / `--warning` + * / `--destructive`), NOT the raw `--dracula-*` primitives. That matters + * for contrast: `:root.light` (Alucard) overrides the semantic tokens to + * AA-safe darker tones (`--success: #14720a`, `--warning: #a34d14`, + * `--destructive: #cb3a2a`) precisely because straight Dracula + * green/orange on a white `--card` clears only ~1.1–1.5:1 — under AA. + * Painting the band off the semantic token means every score surface + * tracks that light-mode correction automatically, in both themes; the + * raw `--dracula-*` primitives do NOT carry a light override. A + * green/yellow/red band is the one semantic the whole v1.10 score + * language extends. */ export type ScoreBand = "green" | "yellow" | "red"; /** Tailwind text-colour class per band (the headline number colour). */ export const BAND_NUMBER_CLASS: Record = { - green: "text-dracula-green", - yellow: "text-dracula-orange", - red: "text-dracula-red", + green: "text-success", + yellow: "text-warning", + red: "text-destructive", }; /** Tailwind background class per band (progress/fill bars). */ export const BAND_PROGRESS_CLASS: Record = { - green: "bg-dracula-green", - yellow: "bg-dracula-orange", - red: "bg-dracula-red", + green: "bg-success", + yellow: "bg-warning", + red: "bg-destructive", }; /** Tailwind border-colour class per band (card / chip outlines). */ export const BAND_BORDER_CLASS: Record = { - green: "border-dracula-green/40", - yellow: "border-dracula-orange/40", - red: "border-dracula-red/40", + green: "border-success/40", + yellow: "border-warning/40", + red: "border-destructive/40", }; /** * The raw CSS variable per band — used where a Recharts `fill` needs an * actual colour string rather than a Tailwind class (the score ring's - * ``). Reads the same `--dracula-*` tokens so dark and - * Alucard light modes track automatically. + * ``). Reads the semantic tokens so dark and Alucard light + * modes track the AA-safe light overrides automatically (the + * `--dracula-*` primitives carry no light override and would fail AA on + * the white card). */ export const BAND_VAR: Record = { - green: "var(--dracula-green)", - yellow: "var(--dracula-orange)", - red: "var(--dracula-red)", + green: "var(--success)", + yellow: "var(--warning)", + red: "var(--destructive)", }; /** diff --git a/src/components/insights/derived/coverage-meter.tsx b/src/components/insights/derived/coverage-meter.tsx index 5cf194da4..500913833 100644 --- a/src/components/insights/derived/coverage-meter.tsx +++ b/src/components/insights/derived/coverage-meter.tsx @@ -48,12 +48,21 @@ export interface CoverageMeterProps { className?: string; } -/** Dot tint per confidence band — reuses the existing dracula vocabulary. */ +/** + * Dot tint per confidence band. Points at the *semantic* feedback tokens + * (`--success` / `--warning` / `--destructive`), not the raw `--dracula-*` + * primitives, so the dots track the AA-safe `:root.light` overrides on the + * white card — straight `bg-dracula-yellow` cleared barely 1.1:1 on white + * and read near-invisible. The four bands collapse onto the three semantic + * severities (medium + low both ride the caution `--warning`); the lit-dot + * count + the percent label carry the medium↔low distinction, never colour + * alone. + */ const BAND_DOT_CLASS: Record = { - high: "bg-dracula-green", - medium: "bg-dracula-yellow", - low: "bg-dracula-orange", - draft: "bg-dracula-red", + high: "bg-success", + medium: "bg-warning", + low: "bg-warning", + draft: "bg-destructive", }; function clampInt(value: number, min: number, max: number): number { diff --git a/src/components/insights/derived/provenance-explainer.tsx b/src/components/insights/derived/provenance-explainer.tsx index 64c217beb..6112c9211 100644 --- a/src/components/insights/derived/provenance-explainer.tsx +++ b/src/components/insights/derived/provenance-explainer.tsx @@ -100,7 +100,7 @@ export function ProvenanceExplainer({
  • {input}
  • @@ -141,7 +141,7 @@ export function ProvenanceExplainer({ href={standard.url} target="_blank" rel="noopener noreferrer" - className="text-dracula-cyan hover:text-dracula-cyan/80 focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded text-[11px] font-medium focus-visible:ring-2 focus-visible:outline-none" + className="text-info hover:text-info/80 focus-visible:ring-ring/50 inline-flex items-center gap-1 rounded text-[11px] font-medium focus-visible:ring-2 focus-visible:outline-none" > {t("insights.derived.provenance.standardLink", { name: standard.name, diff --git a/src/components/insights/derived/score-ring.tsx b/src/components/insights/derived/score-ring.tsx index 06514dc1e..0f75c40e7 100644 --- a/src/components/insights/derived/score-ring.tsx +++ b/src/components/insights/derived/score-ring.tsx @@ -188,7 +188,7 @@ export function ScoreRing({ {!hasScore && ( {t("insights.derived.scoreRing.provisionalCaption")} diff --git a/src/components/insights/derived/sparkline-delta-tile.tsx b/src/components/insights/derived/sparkline-delta-tile.tsx index d95a2fef6..dda91b6ca 100644 --- a/src/components/insights/derived/sparkline-delta-tile.tsx +++ b/src/components/insights/derived/sparkline-delta-tile.tsx @@ -94,11 +94,13 @@ export function SparklineDeltaTile({ // The sparkline tints with the trend sentiment so it reads "same signal" // as the arrow; neutral metrics ride the muted-foreground line. + // Semantic tokens, not raw --dracula-*, so the sparkline tint tracks the + // AA-safe :root.light overrides on the white card (the band-token fix). const strokeVar = arrowSentiment === "positive" - ? "var(--dracula-green)" + ? "var(--success)" : arrowSentiment === "negative" - ? "var(--dracula-orange)" + ? "var(--warning)" : "var(--muted-foreground)"; const fillId = `spark-${label.replace(/[^a-zA-Z0-9]/g, "")}`; diff --git a/src/lib/insights/trend-sentiment.ts b/src/lib/insights/trend-sentiment.ts index 5933f0b14..8a2fa5863 100644 --- a/src/lib/insights/trend-sentiment.ts +++ b/src/lib/insights/trend-sentiment.ts @@ -44,8 +44,12 @@ export function getTrendSentiment( export function sentimentColorClass( direction: TrendSentimentDirection, ): string { - if (direction === "positive") return "text-dracula-green"; - if (direction === "negative") return "text-dracula-orange"; + // Semantic tokens, not raw --dracula-*: `--success` / `--warning` alias + // the same Dracula green/orange in dark mode (pixel-identical there) but + // carry the AA-safe `:root.light` overrides, so the trend arrow + delta + // clear AA on the white card instead of the ~1.2:1 the raw primitives hit. + if (direction === "positive") return "text-success"; + if (direction === "negative") return "text-warning"; return "text-muted-foreground"; }