@import "tailwindcss"; @import "tw-animate-css"; @import "shadcn-vue/tailwind.css"; /* * Theme tokens (design note, not slop-by-default): shadcn-vue's `init` * scaffolds an achromatic "stone" palette (chroma 0 everywhere), a Google * Fonts Inter import, and a Geist Variable heading font. All three are * rewritten below: * * - No Google Fonts import, no Geist. The admin CSP (admin.go) ships * style-src 'self' with no external host allowed, and the panel must load * zero external assets per the operator brief — a network font would * silently fail under that CSP anyway. Headings and body both use the * platform's own UI font stack; numeric/tabular data (usage tables, chart * axes) uses the platform monospace stack for column alignment. * - A deliberate signature color (deep teal, hue ~200) replaces the * grayscale-everything default and the reflexive indigo/purple accent. * - Chart series use the Okabe & Ito (2008) colorblind-safe qualitative * palette rather than picking hues by eye — distinguishable under * protanopia, deuteranopia, and tritanopia alike. * - Dark mode is driven by `prefers-color-scheme`, not a `.dark` class * toggle: this panel has no theme switcher, so the OS setting is the only * signal there is. Tailwind v4's default `dark:` variant already means * "@media (prefers-color-scheme: dark)" — shadcn-vue's init overrides it * to a `.dark`-class variant for apps with a manual theme toggle; that * override is dropped here so any `dark:` utility (ours or a generated * ui/ primitive's) follows the OS setting, same as the CSS variables below. */ @theme inline { --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Consolas, monospace; --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --color-foreground: var(--foreground); --color-background: var(--background); --color-chart-requests: var(--chart-requests); --color-chart-tokens-in: var(--chart-tokens-in); --color-chart-tokens-out: var(--chart-tokens-out); --color-chart-cost: var(--chart-cost); --color-status-warn: var(--status-warn); --color-bar-amber: var(--bar-amber); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); /* Powers the `font-heading` utility Tailwind v4 auto-generates from any --font-* @theme token — CardTitle.vue and AlertTitle.vue apply it (a review fix: they previously wrote the unrecognized class `cn-font-heading`, matching nothing this token or Tailwind's engine generates, so it rendered as a plain no-op string with zero styling effect). Same font as body today; the token exists so a future distinct heading face is a one-line change here, not a find-replace across every component that uses it. */ --font-heading: var(--font-sans); } :root { --background: oklch(0.99 0.004 75); --foreground: oklch(0.19 0.012 75); --card: oklch(0.995 0.003 75); --card-foreground: oklch(0.19 0.012 75); --popover: oklch(0.995 0.003 75); --popover-foreground: oklch(0.19 0.012 75); --primary: oklch(0.46 0.09 200); --primary-foreground: oklch(0.98 0.006 200); --secondary: oklch(0.95 0.006 75); --secondary-foreground: oklch(0.22 0.01 75); --muted: oklch(0.95 0.006 75); --muted-foreground: oklch(0.48 0.012 75); --accent: oklch(0.93 0.02 200); --accent-foreground: oklch(0.28 0.05 200); --destructive: oklch(0.55 0.2 25); --border: oklch(0.9 0.007 75); --input: oklch(0.9 0.007 75); --ring: oklch(0.55 0.09 200); --radius: 0.375rem; /* Chart series — Okabe & Ito (2008) colorblind-safe qualitative set. */ --chart-requests: #009e73; --chart-tokens-in: #0072b2; --chart-tokens-out: #e69f00; --chart-cost: #d55e00; /* * --status-warn: a semantic "degraded, not broken" indicator (the * Providers tab's amber success-rate badge, ProviderRateBadge.vue) — * deliberately its OWN token, not a reuse of --chart-tokens-out despite * sharing that token's hue family. --chart-tokens-out is tuned to read * against a chart's plot background at a small mark size, not as body * text on --card; measured here at #e69f00-on-card it comes out to * ~2.22:1, well under WCAG 1.4.3's 4.5:1 text-contrast minimum — a * genuine accessibility bug on exactly the badge tier meant to say * "something's wrong" (MUST-1, v0.22 review round). #a35c00 on the * light --card measures ~5.06:1. */ --status-warn: #a35c00; /* * --bar-amber: F1 (usage-vs-limit bars, UsageBar.vue) — the filled * amber segment of a budget bar at BAR_AMBER_THRESHOLD (lib/usage- * bars.ts). The track it fills against is --muted (UsageBar.vue's * `bg-muted` on the bar's own background span) — WCAG 1.4.11 non-text * contrast requires 3:1 against that ADJACENT color. * * P8 review fix: this used to match --chart-tokens-out's hue/value * (#e69f00) on the reasoning that it was "already proven legible at * small UI sizes on both --card and --muted" — that reasoning was * never actually checked against --muted specifically, and #e69f00 * measures (WCAG relative-luminance formula, oklch(0.95 0.006 75) * --muted as the track) 1.95:1 there, well under the 3:1 this comment * itself claimed. Reusing --status-warn's own light-mode value * (#a35c00) instead measures 4.44:1 against --muted — comfortably * clears 3:1, and keeps every semantic amber in this panel visually * consistent (same value already used for the "degraded" badge tier, * ProviderRateBadge.vue) rather than adding a third amber shade. */ --bar-amber: #a35c00; } @media (prefers-color-scheme: dark) { :root { --background: oklch(0.17 0.007 75); --foreground: oklch(0.95 0.005 75); --card: oklch(0.21 0.009 75); --card-foreground: oklch(0.95 0.005 75); --popover: oklch(0.21 0.009 75); --popover-foreground: oklch(0.95 0.005 75); --primary: oklch(0.74 0.11 200); --primary-foreground: oklch(0.16 0.012 200); --secondary: oklch(0.27 0.011 75); --secondary-foreground: oklch(0.93 0.005 75); --muted: oklch(0.27 0.011 75); --muted-foreground: oklch(0.68 0.012 75); --accent: oklch(0.32 0.035 200); --accent-foreground: oklch(0.91 0.03 200); --destructive: oklch(0.68 0.19 25); --border: oklch(1 0 0 / 12%); --input: oklch(1 0 0 / 16%); --ring: oklch(0.62 0.1 200); /* Same hues, most already legible on a dark ground at this saturation — Okabe-Ito was designed for slide/print use on light and dark backgrounds alike. */ --chart-requests: #16b891; --chart-tokens-in: #4098d7; --chart-tokens-out: #f0ac1f; --chart-cost: #e8763a; /* #f0ac1f on the dark --card measures ~8.97:1 — no separate dark value was needed to clear WCAG 1.4.3 here, unlike light mode (see --status-warn's own doc comment above), but the token is still redefined per-mode rather than left to fall through, so a reader auditing this block sees every semantic-color token's dark value in one place. */ --status-warn: #f0ac1f; /* Same reasoning as --chart-tokens-out's own dark-mode comment above — legible as-is, redefined here anyway so every semantic-color token's dark value lives in one place. */ --bar-amber: #f0ac1f; } } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; @apply font-sans; font-variant-numeric: tabular-nums; } }