01 · Foundations
The raw material.
Twenty-three color tokens with a light and dark value each, one type family in two voices, eight radius steps, two shadows, five durations. Every swatch and specimen below is rendered live from tokens.css — flip the theme and watch.
Color
One palette, two rooms.
Each token holds a light and a dark value; consumers only ever write the variable. Click any name or hex to copy it. Note what isn't here: no brand blue, no decorative hues. Violet belongs to the agent, green/amber/red belong to the operation.
Surfaces & lines
| Token | Light | Dark | Role |
|---|---|---|---|
| --bg | #ffffff | #08090a | Page background |
| --bg-elev | #ffffff | #101113 | Cards, panels, anything raised |
| --bg-sunken | #fafafa | #0c0d0e | Wells, code blocks, stat cards |
| --line | #e9e9e9 | #1c1d20 | Default hairline border |
| --line-strong | #d8d8d8 | #2a2b2f | Hover borders, emphasized rules |
Ink
| Token | Light | Dark | Role |
|---|---|---|---|
| --fg | #0a0a0a | #f5f5f5 | Headlines, primary text |
| --fg-secondary | #3a3a3a | #b8b8b8 | Body copy in dense UI |
| --fg-muted | #6b6b6b | #8a8a8a | Supporting copy, ledes, metadata |
| --fg-subtle | #9a9a9a | #5a5a5a | Index numbers, faint labels |
Accent — the accent is ink
| Token | Light | Dark | Role |
|---|---|---|---|
| --accent | #0a0a0a | #f5f5f5 | Primary actions — near-black, never a hue |
| --accent-hover | #2a2a2a | #e4e4e4 | Primary action hover |
| --accent-soft | #f4f4f5 | rgba(255,255,255,0.1) | Selected states, quiet emphasis fills |
| --accent-line | #e4e4e7 | rgba(255,255,255,0.18) | Borders on accent-soft surfaces |
| --accent-fg | #ffffff | #0a0a0a | Text on accent fills |
AI — the only hue in the chrome
| Token | Light | Dark | Role |
|---|---|---|---|
| --ai | #6b4fe0 | #b3a4ff | Only where an agent perceives, decides, or acts |
| --ai-soft | #f0ecfb | rgba(179,164,255,0.14) | Agent surface fills |
| --ai-line | #ddd3f7 | rgba(179,164,255,0.3) | Agent surface borders |
Status — real states only
| Token | Light | Dark | Role |
|---|---|---|---|
| --ok | #067647 | #4ade80 | Filled zone, verified check-in, live |
| --ok-soft | #ecfdf3 | rgba(74,222,128,0.12) | ok pill fill |
| --warn | #b45309 | #f59e0b | Zone trending short, silent worker |
| --warn-soft | #fef3c7 | rgba(245,158,11,0.12) | warn pill fill |
| --danger | #b42318 | #f87171 | Short gate, failed credential |
| --danger-soft | #fee4e2 | rgba(248,113,113,0.12) | danger pill fill |
Typography
Geist speaks. Geist Mono reports.
One family, two voices. Sans carries the argument; mono carries the data — timestamps, zone names, fill percentages, anything an operator reads off a radio. Headings run tight (−0.02 to −0.035em) and medium-to-semibold, never bold. tokens.css ships stacks with system fallbacks; load Geist yourself for the full effect.
--font-sans: "Geist", "Geist Variable", ui-sans-serif, system-ui, sans-serif;
--font-mono: "Geist Mono", "Geist Mono Variable", ui-monospace, monospace;
/* eyebrow — the system's smallest, loudest voice */
.eyebrow {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--fg-muted);
}
Radius
4 to 28, then full.
Small elements stay crisp; containers get generous.
Buttons and pills always run --radius-full.
Click a box to copy its variable.
Assignments: inline code 4px · swatches 6px · inputs and nav links 8px · mock rows 12px · stat cards 16px · cards and specimens 20px · photo cutouts and hero media 28px · buttons, pills, chips full.
Layout & spacing
1280 wide, on a 4px grid.
One container: --container = 1280px, with 20 / 24 / 32px gutters as the viewport grows. Sections breathe at 96px vertical (56px on docs pages); cards pad 20–24px. Everything sits on the 4px grid.
Elevation
Two shadows. Borders do the rest.
Depth is mostly a 1px line in this system. Shadows
exist for two jobs only: pop lifts
floating chrome (toasts, mega panels), cmd
anchors product mockups so they read as windows. Both are black-based
and shared across themes — in dark they read as deeper ambient shade,
and the slightly stronger dark hairlines carry the edges.
--shadow-pop: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px -8px rgba(0,0,0,0.4); --shadow-cmd: 0 1px 2px rgba(0,0,0,0.4), 0 16px 48px -16px rgba(0,0,0,0.8);
Motion
Motion states facts.
Four moves, each with one meaning: fade-up reveals
content as you reach it, pulse-dot means live data, marquee carries
long lists, aurora drift warms empty backgrounds. Ease is
--ease-out-expo; hovers resolve in 150ms;
reveals in 600ms. Everything respects
prefers-reduced-motion.
fade-up · 600ms · ease-out-expo
pulse-dot · 2s · means live
marquee · 40s · linear · edge-faded
aurora drift · 16–20s · hero backgrounds only
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); --dur-fast: 150ms; /* hovers, toggles */ --dur-med: 300ms; /* accordions, panels */ --dur-reveal: 600ms; /* fade-up on scroll */ --dur-pulse: 2s; /* live dots */ --dur-marquee: 40s; /* ticker loops */
Grain
Grain kills the banding.
Every editorial gradient wears a fine SVG-noise
overlay at 3.5% opacity — enough to break up banding and give flat
color a printed feel, never enough to read as texture. Add class
.grain to any positioned element.
without grain
with .grain
.grain { position: relative; }
.grain::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
opacity: 0.035;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}