---
version: alpha
name: Saturation
description: The Saturation design system — production-grade UI for film and video production tooling. Powers ui.saturation.io, saturation.io (marketing), and the Saturation app.
colors:
  # generated:colors-start
  background: "#FCFCFC"
  foreground: "#09090B"
  card: "#FCFCFC"
  card-foreground: "#27272A"
  popover: "#FAFAFA"
  popover-foreground: "#09090B"
  primary: "#1B4DC4"
  primary-foreground: "#FFFFFF"
  secondary: "#E4E4E7"
  secondary-foreground: "#18181B"
  muted: "#F4F4F5"
  muted-foreground: "#71717B"
  accent: "#F4F4F5"
  accent-foreground: "#3F3F46"
  destructive: "#E7000B"
  destructive-foreground: "#FFFFFF"
  border: "#F4F4F5"
  input: "#D4D4D8"
  ring: "#000000"
  chart-1: "#A684FF"
  chart-2: "#51A2FF"
  chart-3: "#FF8904"
  chart-4: "#FB64B6"
  chart-5: "#9F9FA9"
  sidebar: "#FCFCFC"
  sidebar-foreground: "#18181B"
  sidebar-primary: "#18181B"
  sidebar-primary-foreground: "#FAFAFA"
  sidebar-accent: "#F4F4F5"
  sidebar-accent-foreground: "#09090B"
  sidebar-border: "#EBEBEB"
  sidebar-ring: "#A1A1A1"
  badge-red-bg: "#FEE2E2"
  badge-red-text: "#B91C1C"
  badge-cyan-bg: "#CFFAFE"
  badge-cyan-text: "#0E7490"
  badge-amber-bg: "#FEF3C7"
  badge-amber-text: "#B45309"
  badge-green-bg: "#DCFCE7"
  badge-green-text: "#15803D"
  badge-gray-bg: "#F3F4F6"
  badge-gray-text: "#374151"
  badge-yellow-bg: "#FEF9C3"
  badge-yellow-text: "#A16207"
  badge-blue-bg: "#DBEAFE"
  badge-blue-text: "#1D4ED8"
  badge-purple-bg: "#F3E8FF"
  badge-purple-text: "#7E22CE"
  brand-citizens: "#006A4D"
  brand-truist: "#4F2683"
  brand-ally: "#720096"
  brand-hsbc: "#DB0011"
  brand-quickbooks: "#2CA01C"
  brand-xero: "#13B5EA"
  brand-netsuite: "#403C38"
  brand-sage: "#00AF4A"
  brand-freshbooks: "#0075DD"
  # Dark mode pairs — DESIGN.md spec doesn't natively support multi-theme,
  # so dark values are exposed as `dark-*` siblings. Resolve the dark variant
  # of any token by prefixing its name with `dark-`. Keep parity with `.dark`
  # block in packages/react/src/styles/global.css (canonical runtime values are oklch).
  dark-background: "#09090B"
  dark-foreground: "#FFFFFF"
  dark-card: "#09090B"
  dark-card-foreground: "#FAFAFA"
  dark-popover: "#09090B"
  dark-popover-foreground: "#FAFAFA"
  dark-primary: "#2158DD"
  dark-primary-foreground: "#FFFFFF"
  dark-secondary: "#18181B"
  dark-secondary-foreground: "#F4F4F5"
  dark-muted: "#18181B"
  dark-muted-foreground: "#9F9FA9"
  dark-accent: "#18181B"
  dark-accent-foreground: "#D4D4D8"
  dark-destructive: "#FF6467"
  dark-destructive-foreground: "#FAFAFA"
  dark-border: "#27272A"
  dark-input: "#27272A"
  dark-ring: "#27272A"
  dark-chart-1: "#4F39F6"
  dark-chart-2: "#155DFC"
  dark-chart-3: "#F54900"
  dark-chart-4: "#E60076"
  dark-chart-5: "#FAFAFA"
  dark-sidebar: "#09090B"
  dark-sidebar-foreground: "#FFFFFF"
  dark-sidebar-primary: "#FFFFFF"
  dark-sidebar-primary-foreground: "#FFFFFF"
  dark-sidebar-accent: "#18181B"
  dark-sidebar-accent-foreground: "#FFFFFF"
  dark-sidebar-border: "#18181B"
  dark-sidebar-ring: "#18181B"
  dark-badge-red-bg: "#7F1D1D"
  dark-badge-red-text: "#FECACA"
  dark-badge-cyan-bg: "#164E63"
  dark-badge-cyan-text: "#A5F3FC"
  dark-badge-amber-bg: "#78350F"
  dark-badge-amber-text: "#FDE68A"
  dark-badge-green-bg: "#14532D"
  dark-badge-green-text: "#BBF7D0"
  dark-badge-gray-bg: "#374151"
  dark-badge-gray-text: "#E5E7EB"
  dark-badge-yellow-bg: "#713F12"
  dark-badge-yellow-text: "#FEF08A"
  dark-badge-blue-bg: "#1E3A8A"
  dark-badge-blue-text: "#BFDBFE"
  dark-badge-purple-bg: "#581C87"
  dark-badge-purple-text: "#E9D5FF"
  # generated:colors-end
typography:
  display:
    fontFamily: Gellix
    fontSize: 3rem
    fontWeight: 600
    lineHeight: 1.1
    letterSpacing: -0.03em
  h1:
    fontFamily: Gellix
    fontSize: 1.875rem
    fontWeight: 600
    lineHeight: 1.2
    letterSpacing: -0.025em
  h2:
    fontFamily: Gellix
    fontSize: 1.25rem
    fontWeight: 500
    lineHeight: 1.4
    letterSpacing: -0.02em
  h3:
    fontFamily: Gellix
    fontSize: 1.125rem
    fontWeight: 500
    lineHeight: 1.556
  body:
    fontFamily: Gellix
    fontSize: 0.9375rem
    fontWeight: 400
    lineHeight: 1.625
  body-lg:
    fontFamily: Gellix
    fontSize: 1rem
    fontWeight: 400
    lineHeight: 1.5
  label:
    fontFamily: Gellix
    fontSize: 0.875rem
    fontWeight: 500
    lineHeight: 1.4
  caption:
    fontFamily: Gellix
    fontSize: 0.75rem
    fontWeight: 500
    lineHeight: 1.4
  cell:
    fontFamily: Gellix
    fontSize: 0.8125rem
    fontWeight: 400
    lineHeight: 1.45
  code:
    fontFamily: Geist Mono
    fontSize: 0.875rem
    fontWeight: 400
    lineHeight: 1.5
  code-inline:
    fontFamily: Geist Mono
    fontSize: 0.8em
    fontWeight: 400
    lineHeight: 1.5
rounded:
  none: 0px
  sm: 0.4rem
  md: 0.675rem
  lg: 0.8rem
  xl: 1.05rem
  full: 9999px
spacing:
  "0": 0px
  "1": 0.25rem
  "2": 0.5rem
  "3": 0.75rem
  "4": 1rem
  "5": 1.25rem
  "6": 1.5rem
  "8": 2rem
  "10": 2.5rem
  "12": 3rem
  "16": 4rem
  "20": 5rem
  "24": 6rem
components:
  # generated:components-start
  # ── Action: buttons ────────────────────────────────────────────
  button-primary:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.primary-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-primary-hover:
    backgroundColor: "#5379D2"  # primary @ 75% on background — bg-primary/75
    textColor: "{colors.primary-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-primary-disabled:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.primary-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-secondary:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-secondary-hover:
    backgroundColor: "{colors.accent}"
    textColor: "{colors.accent-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-destructive:
    backgroundColor: "{colors.destructive}"
    textColor: "{colors.destructive-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-destructive-hover:
    backgroundColor: "#E91923"  # destructive @ 90% on background — bg-destructive/90
    textColor: "{colors.destructive-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-outline:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-outline-hover:
    backgroundColor: "{colors.foreground}"
    textColor: "{colors.background}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-ghost:
    backgroundColor: "{colors.background}"
    textColor: "{colors.muted-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-ghost-hover:
    backgroundColor: "{colors.accent}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    height: 2.25rem
    padding: 0.5rem 1rem
  button-link:
    backgroundColor: "{colors.background}"
    textColor: "{colors.muted-foreground}"
    rounded: "{rounded.none}"
    typography: "{typography.label}"
    padding: 0px
  # ── Containers & surfaces ─────────────────────────────────────
  card:
    backgroundColor: "{colors.card}"
    textColor: "{colors.card-foreground}"
    rounded: "{rounded.lg}"
    padding: 1.5rem
  dialog:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.lg}"
    padding: 1.5rem
  popover:
    backgroundColor: "{colors.popover}"
    textColor: "{colors.popover-foreground}"
    rounded: "{rounded.md}"
    padding: 0.75rem
  tooltip:
    backgroundColor: "{colors.foreground}"
    textColor: "{colors.background}"
    rounded: "{rounded.sm}"
    typography: "{typography.caption}"
    padding: 0.375rem 0.625rem
  alert:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.lg}"
    padding: 1rem
  alert-destructive:
    backgroundColor: "{colors.background}"
    textColor: "{colors.destructive}"
    rounded: "{rounded.lg}"
    padding: 1rem
  # ── Form controls ────────────────────────────────────────────
  input:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.body}"
    height: 2.25rem
    padding: 0.25rem 0.75rem
  input-focus:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.body}"
    height: 2.25rem
    padding: 0.25rem 0.75rem
  input-disabled:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.muted-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.body}"
    height: 2.25rem
    padding: 0.25rem 0.75rem
  textarea:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.body}"
    padding: 0.5rem 0.75rem
  checkbox:
    backgroundColor: "{colors.background}"
    rounded: "{rounded.sm}"
    size: 1rem
  checkbox-checked:
    backgroundColor: "{colors.primary}"
    textColor: "{colors.primary-foreground}"
    rounded: "{rounded.sm}"
    size: 1rem
  switch:
    backgroundColor: "{colors.input}"
    rounded: "{rounded.full}"
    height: 1.25rem
    width: 2.25rem
  switch-checked:
    backgroundColor: "{colors.primary}"
    rounded: "{rounded.full}"
    height: 1.25rem
    width: 2.25rem
  # ── Navigation & display ─────────────────────────────────────
  badge:
    backgroundColor: "{colors.secondary}"
    textColor: "{colors.secondary-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.caption}"
    padding: 0.125rem 0.5rem
  separator:
    backgroundColor: "{colors.border}"
    height: 1px
  sidebar-item:
    backgroundColor: "{colors.sidebar}"
    textColor: "{colors.sidebar-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    padding: 0.375rem 0.75rem
  sidebar-item-active:
    backgroundColor: "{colors.sidebar-accent}"
    textColor: "{colors.sidebar-accent-foreground}"
    rounded: "{rounded.md}"
    typography: "{typography.label}"
    padding: 0.375rem 0.75rem
  table-row:
    backgroundColor: "{colors.background}"
    textColor: "{colors.foreground}"
    typography: "{typography.body}"
  table-row-hover:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.foreground}"
    typography: "{typography.body}"
  table-header:
    backgroundColor: "{colors.background}"
    textColor: "{colors.muted-foreground}"
    typography: "{typography.label}"
  kbd:
    backgroundColor: "{colors.muted}"
    textColor: "{colors.muted-foreground}"
    rounded: "{rounded.sm}"
    typography: "{typography.caption}"
    padding: 0.125rem 0.375rem
  # generated:components-end
---

# Saturation Design System

This document is the canonical specification for Saturation's visual identity. It is the source of truth that both humans and AI agents read to make consistent design decisions across the registry (`ui.saturation.io`), the marketing site (`saturation.io`), and the Saturation product app.

## Overview

Saturation is production-grade tooling for film and video production. The design language reflects what production teams need from their software: **calm, dense, and unobtrusive**. The interface should disappear in favor of the work — schedules, budgets, call sheets, contacts. It is professional, not playful; precise, not decorative.

**Voice.** Direct, lower-case-friendly, technical without being clinical. Borrowed cues from production-craft tools (Avid, DaVinci, the call-sheet) more than from consumer SaaS.

**Density.** Dense by default. Production users live in tables and lists with hundreds of rows. Generous whitespace is reserved for marketing surfaces (saturation.io); product surfaces favor information density and predictable rhythms over breathing room.

**Color space.** Tokens in this file are written as `#hex` for sRGB compatibility with the `@google/design.md` tooling. The runtime canonical values live as `oklch()` in `packages/react/src/styles/global.css` for perceptually-uniform interpolation and dark-mode parity. **When a token value disagrees, `global.css` wins.** The hex values here are computed from oklch via `scripts/oklch-to-hex.ts`.

**Two modes.** Light mode tokens are the un-prefixed values above. Dark mode tokens are exposed as `dark-*` siblings in the same `colors:` map (`dark-primary`, `dark-foreground`, etc.) — the spec doesn't natively support multi-theme, so we use a prefix convention. Both light and dark are auto-synced from `packages/react/src/styles/global.css` by `scripts/sync-design-tokens.ts`. Canonical runtime values live in `.dark` of `global.css` as oklch.

**State variants.** Interaction states (hover, focus, active, disabled) are encoded as separate component recipes with name suffixes — `button-primary-hover`, `input-focus`, `sidebar-item-active`, etc. — matching the spec's idiomatic pattern from the Heritage and Totality Festival reference designs. Opacity-based hovers (e.g. `bg-primary/75`) are precomputed against the `background` surface and stored as 6-digit hex; the variants therefore look correct on a typical surface but may drift on inverted or colored surfaces.

## Colors

The palette is built around a single high-saturation blue accent against a neutral zinc grayscale. There is no gratuitous color; chart palettes are the only place we reach beyond the system.

### Semantic roles

- **Primary (`#1B4DC4`)** — Saturation Blue. The single interactive accent. Used for primary buttons, links, focus rings, and selection states. **Never** decorative; if it's blue, it's interactive or selected.
- **Foreground (`#09090B`)** — near-black. Body text and high-contrast UI.
- **Background (`#FCFCFC`)** — off-white. The page floor.
- **Muted (`#F4F4F5`) / Muted-foreground (`#71717B`)** — secondary surfaces and supporting text.
- **Border (`#F4F4F5`)** — soft separators. Same value as muted for a cohesive flat aesthetic.
- **Input (`#D4D4D8`)** — input borders, slightly stronger than `border` to communicate interactivity.
- **Destructive (`#E7000B`)** — used only for irreversible destructive actions (delete, remove, force). Not for general errors — use form-level error states for those.
- **Accent (`#F4F4F5`) / Accent-foreground (`#3F3F46`)** — hover states and subtle emphasis.

### Charts

The chart palette is deliberately wide-gamut — purple, blue, orange, pink, gray — to maximize legibility across many concurrent series in budget and schedule visualizations. **Never** use chart colors for UI chrome.

### Badges

A semantic badge palette (`badge-red`, `badge-cyan`, `badge-amber`, `badge-green`, `badge-gray`, `badge-yellow`, `badge-blue`, `badge-purple`) maps to status concepts in the product (e.g. amber = pending approval, green = confirmed, red = canceled). Badges have explicit dark-mode pairs because their saturation needs adjustment, not just inversion.

### Dark mode

Dark mode is not an inversion — it is its own designed palette. Background drops to `#09090B`, primary brightens to `#2158DD` to maintain perceived saturation against the dark backdrop, and destructive shifts to `#FF6467` for the same reason. See `.dark` in `packages/react/src/styles/global.css`.

## Typography

Fonts are **configurable per surface** via the **`font-provider`** preset (`next` / `gellix` / `marketing`), which scopes `--font-sans` and `--font-display` over a subtree. The **product app** (next-web) renders in the **system sans** stack (`ui-sans-serif, system-ui, sans-serif`); it does not load Gellix. **Marketing** (saturation.io) loads **Gellix** and applies it to display headings via the `marketing` preset (system body, Gellix display); the `gellix` preset puts Gellix on body too. **Geist Mono** is used for code, numerical data, and timestamps on every surface. Georgia is a serif fallback for marketing prose only. The `fontFamily` values in the `typography:` block below name each role's default family in the registry/marketing context; the product overrides the sans roles to the system stack via the `next` preset.

### Scale

The type scale is tight: nine primary roles. We do not multiply roles for marketing flourish.

- **`display`** (3rem / 600) — marketing hero only. Not used in product.
- **`h1`** (1.875rem / 600) — page title in product; section title in marketing.
- **`h2`** (1.25rem / 500) — section heading.
- **`h3`** (1.125rem / 500) — subsection.
- **`body`** (0.9375rem / 400) — default UI text and content body. Tighter than the marketing default.
- **`body-lg`** (1rem / 400) — lead paragraphs and the page-description line under an `h1`.
- **`label`** (0.875rem / 500) — buttons, form labels, table headers.
- **`caption`** (0.75rem / 500) — metadata, timestamps, status text.
- **`cell`** (0.8125rem / 400): grid and table cell + column header, the dense product workhorse. System sans, not used in marketing.
- **`code`** / **`code-inline`** — Geist Mono. Inline code uses `0.8em` to read as integrated prose, not a callout.

### Letter-spacing

Headlines tighten (`-0.025em` / `-0.02em`) for optical density. Body and label text use the default. We never positive-letter-space ALL CAPS — uppercase is reserved for badge text and uses normal spacing.

## Layout

The product is structured around a **persistent left sidebar + main content** split. The marketing site is more conventional — full-width sections with content centered.

### Layout tokens

- `--header-height: 3.5rem` (56px) — top app bar.
- `--sidebar-width: 18rem` (288px) — primary navigation.
- `--sidebar-menu-width: 14rem` (224px) — secondary nav within a section.
- `--spacing: 0.25rem` (4px) — base unit. All Tailwind spacing utilities derive from this.

### Container

The marketing surface uses `.container-wrapper` (`mx-auto w-full max-w-screen-2xl`, 1536px). The product surface does not constrain width — it fills the viewport, since most users dock the app full-width on a single monitor.

### Breakpoints

Standard Tailwind: `sm 640`, `md 768`, `lg 1024`, `xl 1280`, `2xl 1536`. Mobile is supported for marketing and read-only product views (call sheets); core production tooling targets desktop.

### Density

Default row heights aim at ~36px (8 spacing units). Tables and list rows go to 32px (`py-2`) for high-density data. Avoid going below 28px — touch targets and click zones suffer.

## Elevation & Depth

Saturation is a **flat** system. We use border separation and background color shifts before we reach for shadow.

- **Surface levels.** `background` (page) → `card`/`popover` (elevated content) → `accent` (hover, selected). Levels are distinguished by a 1-2% lightness step in oklch, not by drop shadow.
- **Borders.** All elevated surfaces have a 1px `border` color border. This is the primary separation cue.
- **Shadows.** Reserved for floating overlays only — popovers, dialogs, dropdowns. Use the Tailwind `shadow-md` / `shadow-lg` defaults; do not author bespoke shadows.
- **Z-index.** Defer to component primitives (Radix manages layering for portals). Do not introduce custom z-index outside of Radix layers.

## Shapes

Radius is uniform and small. There are no circles outside of avatars and toggle dots, no ovals, no asymmetric corners.

- **`rounded.sm` (0.4rem)** — small interactive elements: chips, tag-removal buttons, segmented controls.
- **`rounded.md` (0.675rem)** — buttons, inputs, badges, dropdown items. **The default for interactive UI.**
- **`rounded.lg` (0.8rem)** — cards, dialogs, popovers, the canonical `--radius`.
- **`rounded.xl` (1.05rem)** — code blocks and large surface tiles.
- **`rounded.full`** — avatars and the rare circular indicator.

The base `--radius` is `0.8rem` and all other steps derive from it (Tailwind's `--radius-sm/md/lg/xl` formulas in `global.css`). Changing the base value rescales the entire system.

## Components

Component recipes live in the YAML frontmatter under `components:` (hand-written, encodes design intent). The inventory below is auto-derived from [`lib/components.registry.ts`](./lib/components.registry.ts) by `pnpm generate:design`. Live previews and full prop tables are in the docs at [/docs/components](https://ui.saturation.io/docs/components).

Component tokens compose from base tokens via `{token.path}` references — never hardcoded values. When adding a new component recipe, the rule is: if a property could conceivably theme, it must reference a token, not a literal.

### Inventory

<!-- generated:inventory-start -->
_79 components across 9 categories. Last generated by `pnpm generate:design`._

### Navigation (6)

| Component | Description |
| --- | --- |
| [`breadcrumb`](https://ui.saturation.io/docs/components/breadcrumb) | Displays the path to the current page location and allows navigation back. |
| [`command`](https://ui.saturation.io/docs/components/command) | Fast, composable command menu for React. Perfect for search, quick actions, and Cmd+K interfaces. |
| [`menubar`](https://ui.saturation.io/docs/components/menubar) | A horizontal menu bar for navigation. Perfect for application-style navigation. |
| [`navigation-menu`](https://ui.saturation.io/docs/components/navigation-menu) | A navigation menu with viewport, trigger, content, and link sub-components. |
| [`pagination`](https://ui.saturation.io/docs/components/pagination) | A navigation component for navigating through pages of content. |
| [`tabs`](https://ui.saturation.io/docs/components/tabs) | A set of layered sections of content—known as tab panels—displayed one at a time. |

### Overlays (5)

| Component | Description |
| --- | --- |
| [`collapsible`](https://ui.saturation.io/docs/components/collapsible) | An expandable content container that can be toggled open or closed. |
| [`context-menu`](https://ui.saturation.io/docs/components/context-menu) | Displays a menu triggered by right-click. |
| [`dialog`](https://ui.saturation.io/docs/components/dialog) | A modal dialog that overlays the main content. |
| [`dropdown-menu`](https://ui.saturation.io/docs/components/dropdown-menu) | Displays a menu triggered by a button. |
| [`sheet`](https://ui.saturation.io/docs/components/sheet) | A slide-over dialog that overlays the main content from one side of the screen. |

### Layout (3)

| Component | Description |
| --- | --- |
| [`button-group`](https://ui.saturation.io/docs/components/button-group) | Groups related buttons together. |
| [`font-provider`](https://ui.saturation.io/docs/components/font-provider) | Swappable font-token layer. Wrap a subtree to scope --font-sans, --font-mono, --font-display, and --font-feature-settings via CSS-variable cascade. Presets: `next` (all system, the product default), `gellix` (all Gellix), `marketing` (system body, Gellix display, saturation.io). The product app uses `next` / the system `--font-sans` and never loads Gellix. Controls *which* font the Typography primitives (T1-T4, P, etc.) render in. |
| [`wizard-split-layout`](https://ui.saturation.io/docs/components/wizard-split-layout) | A two-column wizard layout with an interactive side and a responsive display panel. |

### Animation (16)

| Component | Description |
| --- | --- |
| [`animated-group`](https://ui.saturation.io/docs/components/animated-group) | Animates a group of elements with staggered entry/exit. |
| [`animated-list`](https://ui.saturation.io/docs/components/animated-list) | A dynamic list that animates items in and out with spring physics. Supports scale, slide, fade, and bounce animation styles. |
| [`animated-number`](https://ui.saturation.io/docs/components/animated-number) | Animates a number value with smooth transitions. |
| [`beam`](https://ui.saturation.io/docs/components/beam) | Animated traveling-glow border effect. Wraps any element with a multi-layer beam that respects border-radius, theme, and interactivity. Adapted from border-beam by Jakub Antalik (MIT). |
| [`blur-fade`](https://ui.saturation.io/docs/components/blur-fade) | Blur + fade entrance animation for individual elements. Supports scroll-triggered reveal and configurable direction. |
| [`border-trail`](https://ui.saturation.io/docs/components/border-trail) | Animated border trail effect. |
| [`glow-effect`](https://ui.saturation.io/docs/components/glow-effect) | Adds a glow effect around elements. |
| [`liquid-metal`](https://ui.saturation.io/docs/components/liquid-metal) | WebGL-powered liquid metallic effect with animated metaballs. Supports dark/light mode, configurable blob count, speed, hue cycling, and smooth fade-in. |
| [`loading-state`](https://ui.saturation.io/docs/components/loading-state) | Full-screen splash loader with a glowing brand logo, fake-progress bar, and shimmer text. Pair with the useLoading hook for automatic progress management. |
| [`parallax`](https://ui.saturation.io/docs/components/parallax) | A scroll-driven parallax wrapper with GPU-accelerated transforms and reduced motion support. |
| [`pixel`](https://ui.saturation.io/docs/components/pixel) | Interactive canvas-based pixel grid that animates on load with configurable reveal patterns. Supports dark/light mode colors and multiple animation directions. |
| [`progressive-blur`](https://ui.saturation.io/docs/components/progressive-blur) | A progressive blur effect that creates a smooth gradient blur transition using multiple backdrop-filter layers. |
| [`ripple`](https://ui.saturation.io/docs/components/ripple) | Animated concentric ripple rings that pulse behind content for emphasis. Pure CSS animation, no JS dependencies. |
| [`spotlight`](https://ui.saturation.io/docs/components/spotlight) | Creates a spotlight effect that follows cursor. |
| [`text-effect`](https://ui.saturation.io/docs/components/text-effect) | Animated text reveal effects. |
| [`text-shimmer`](https://ui.saturation.io/docs/components/text-shimmer) | Shimmering text effect for loading states or emphasis. |

<!-- generated:inventory-end -->

## Do's and Don'ts

### Notes on the spec format

The spec format does not currently encode borders, opacity modifiers, hover/focus/active states, transitions, or shadows. These must be inferred from prose:

- `button-secondary` and `button-outline` have a 1px `border` (color: `{colors.input}` or `{colors.border}`). This is implicit in the variant name; the visible "outline" look comes from the border, not the background.
- `button-ghost` has no background and no border — text-only, picks up `{colors.accent}` on hover.
- All buttons use the spec's `height: 2.25rem` (36px) and `borderRadius: rounded.md` (10.8px). The `rounded.md` value is computed as `calc(var(--radius) - 2px)` from the runtime `--radius: 0.8rem`.
- Hover states for primary/destructive buttons darken the background by ~10%.
- Focus-visible adds a 2px outline using `{colors.ring}` with a 2px offset.

### Do

- **Do** use semantic tokens (`bg-primary`, `text-muted-foreground`) instead of palette indices (`bg-blue-600`, `text-zinc-500`).
- **Do** use the `radix-ui` monorepo namespace imports (`import { Slot } from "radix-ui"`), not `@radix-ui/react-*` per-package imports.
- **Do** use `cn()` to compose className conditionals and `cva()` for variant-driven components.
- **Do** keep marketing density airy and product density tight. They share tokens; they differ in spacing rhythm.
- **Do** edit `packages/react/src/styles/global.css` and this `DESIGN.md` together when changing a token. Run `pnpm design:lint` to catch drift.
- **Do** treat `oklch()` in `global.css` as canonical and the hex values here as a derived export.

### Don't

- **Don't** hardcode color values in components. If you find yourself writing `#hex` or `rgb()` outside of `global.css` or `DESIGN.md`, you are introducing drift.
- **Don't** introduce a NEW font. The system uses three families: system-sans (product UI), Gellix (marketing display and the `gellix` preset), and Geist Mono (code and numerics everywhere). Switch between them only via `font-provider` presets; never hardcode a `font-family` or a `var(--font-gellix)` literal in product code. Marketing motion graphics or one-off illustrations may use anything; the *interface* uses these families.
- **Don't** add bespoke shadow values. Use the Tailwind defaults and only on floating overlays.
- **Don't** reach for chart colors to brighten UI chrome.
- **Don't** use `destructive` for general errors. It is reserved for irreversible actions.
- **Don't** edit files in `public/r/` or `content/docs/components/` by hand — they are generated. See the registry workflow in `.claude/rules/registry-workflow.md`.
- **Don't** add Tailwind classes that bypass tokens (e.g. `bg-[#1B4DC4]`). The arbitrary-value escape hatch should be exceedingly rare and accompanied by a comment explaining why a token won't do.
