Files
guru-connect/dashboard/src/styles/tokens.css
Mike Swanson 43a9432b81
All checks were successful
Build and Test / Build Agent (Windows) (push) Successful in 6m56s
Build and Test / Build Server (Linux) (push) Successful in 10m15s
Build and Test / Security Audit (push) Successful in 4m12s
Build and Test / Build Summary (push) Successful in 10s
feat(dashboard): GuruConnect v2 operator console (pass 1)
React + Vite + TypeScript SPA: scaffold, operations-terminal design
system, Bearer-token auth, and the Machines view.

- Design system: OKLCH-tinted dark theme (ink-slate + signal-cyan),
  Hanken Grotesk + JetBrains Mono, status-color language
  (online/offline/granted/pending/denied/not_required), motion with
  prefers-reduced-motion honored.
- Auth: token in sessionStorage via ref (never React state), protected
  routes, 401 session teardown, admin-gated per-agent-key UI.
- Machines view: data table (sticky header, keyboard-activated rows,
  skeleton loading, actionable empty/error states), non-blocking detail
  drawer, delete confirm, admin key management with copy-once reveal.
- UI primitives: Modal (focus trap + inert + portal + dialogStack),
  Drawer, Table, Badge/StatusDot, toast, states.
- Typed API client normalizing the two error-envelope shapes.

Passed Code Review (no blockers), impeccable critique-and-polish, and
local gates (tsc/lint/build green). Dev-only Vite proxy to :3002.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 12:51:11 -07:00

224 lines
5.1 KiB
CSS

/*
* GuruConnect "Operations terminal" design tokens.
* Dark control-room console. Not a generic dashboard.
*/
:root {
/*
* Palette is OKLCH. Every neutral is tinted toward the signal-cyan brand hue
* (~185deg) at a low chroma so the slate surfaces feel cohesive with the
* accent without reading as "colored". Lightness drives the surface scale;
* chroma drops near the extremes so nothing goes garish.
*/
--brand-hue: 184;
/* Surfaces — dark control room, lighter = more elevated */
--bg: oklch(17% 0.012 var(--brand-hue));
--panel: oklch(22.5% 0.014 var(--brand-hue));
--panel-2: oklch(19.5% 0.013 var(--brand-hue));
--border: oklch(28% 0.014 var(--brand-hue));
--border-strong: oklch(34% 0.016 var(--brand-hue));
/* Text — tinted toward brand, AA-verified on --panel and --panel-2 */
--text: oklch(93% 0.008 var(--brand-hue));
--text-muted: oklch(70% 0.014 var(--brand-hue));
--text-faint: oklch(62% 0.016 var(--brand-hue));
/* Accent — signal cyan */
--accent: oklch(78% 0.13 184);
--accent-press: oklch(70% 0.12 184);
--accent-ink: oklch(24% 0.06 184); /* text on accent fills */
--accent-soft: oklch(78% 0.13 184 / 0.12);
--accent-ring: oklch(78% 0.13 184 / 0.4);
/* Status color language */
--ok: oklch(74% 0.16 150);
--warn: oklch(80% 0.14 86);
--bad: oklch(66% 0.19 27);
--neutral: oklch(62% 0.02 var(--brand-hue));
--ok-soft: oklch(74% 0.16 150 / 0.15);
--warn-soft: oklch(80% 0.14 86 / 0.15);
--bad-soft: oklch(66% 0.19 27 / 0.16);
--bad-line: oklch(66% 0.19 27 / 0.42);
--neutral-soft: oklch(62% 0.02 var(--brand-hue) / 0.14);
/* Typography */
--font-ui: "Hanken Grotesk", system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;
/* Radii */
--radius-sm: 5px;
--radius: 8px;
--radius-lg: 12px;
/* Elevation — shadow carries the brand-tinted near-black, never pure #000. */
--shadow-ink: oklch(12% 0.02 var(--brand-hue));
--shadow-1: 0 1px 2px oklch(12% 0.02 var(--brand-hue) / 0.4);
--shadow-2: 0 8px 24px oklch(12% 0.02 var(--brand-hue) / 0.45);
--shadow-pop: 0 16px 48px oklch(12% 0.02 var(--brand-hue) / 0.6);
/* Layout */
--sidebar-w: 224px;
--topbar-h: 56px;
--row-h: 38px;
/* Motion — ease-out only (no bounce). --ease-out is a sharper expo curve
reserved for reveals (drawer, toast); --ease is the everyday transition. */
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--dur-fast: 120ms;
--dur: 180ms;
--dur-panel: 240ms;
/* z-index scale — semantic, no magic numbers */
--z-sticky: 200;
--z-drawer: 300;
--z-modal: 400;
--z-toast: 500;
}
* {
box-sizing: border-box;
}
html,
body,
#root {
height: 100%;
margin: 0;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font-ui);
font-size: 14px;
/* Light text on dark reads heavier; trim weight slightly for even color. */
font-weight: 400;
line-height: 1.45;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
a {
color: var(--accent);
text-decoration: none;
}
button {
font-family: inherit;
}
::selection {
background: var(--accent-ring);
color: var(--text);
}
/* Scrollbar — keep it console-quiet */
* {
scrollbar-width: thin;
scrollbar-color: var(--border-strong) transparent;
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-thumb {
background: var(--border-strong);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
.mono {
font-family: var(--font-mono);
font-feature-settings: "ss01", "zero";
}
/* Screen-reader-only utility (announce state that's otherwise visual). */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
/* Global keyboard focus ring for interactive elements without a bespoke one. */
:where(a, [tabindex]):focus-visible {
outline: none;
box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent-ring);
border-radius: var(--radius-sm);
}
input[type="checkbox"]:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
/* Consent-pending pulse (status language) */
@keyframes gc-pulse {
0%,
100% {
box-shadow: 0 0 0 0 var(--warn-soft);
opacity: 1;
}
50% {
box-shadow: 0 0 0 4px transparent;
opacity: 0.55;
}
}
/* Live relay indicator pulse */
@keyframes gc-live {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.35;
}
}
/* Staggered row fade-in for the data table */
@keyframes gc-row-in {
from {
opacity: 0;
transform: translateY(3px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Drawer slide-in from the right edge */
@keyframes gc-drawer-in {
from {
transform: translateX(16px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Skeleton shimmer for loading rows */
@keyframes gc-shimmer {
to {
background-position: 180% 0;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation: none !important;
transition: none !important;
}
}