# 02 · Tokens

The values live in **`tokens.css`** (web) and **`tokens.json`** (platform-neutral, for SwiftUI and Compose). This page explains them: what each is for, where it came from, and which decisions were taken. Values in tables are light / dark.

Source key: **c6** = 6-liquid-glass, **c2** = 2-beautiful, **c8** = 8-glass-pipeline, **c10** = 10-calendar-glass, **iOS** = the shipping app's DesignSystem, **new** = added for readability or accessibility.

## How theming works

- Light is the default. Dark applies when the OS is dark, unless `<html data-theme="light">` forces light. `<html data-theme="dark">` forces dark (used by the prototype's theme switch).
- `prefers-reduced-transparency`, `prefers-contrast: more` and `prefers-reduced-motion` each override tokens. The prototype can force them with `data-transparency="reduced"`, `data-contrast="more"` and `data-motion="reduced"`.
- Components only read tokens, so these switches restyle the whole app without touching component CSS.

## Colour

### Text

| Token | Light | Dark | Use | Source |
|---|---|---|---|---|
| `--ink` | `#0d0f1a` | `#f4f5fb` | Primary text, icons on glass | c6 |
| `--text-2` | ink @ 62% | ink @ 66% | Secondary text on solid/frosted only | c6 `--mut` (.58 → .62, new) |
| `--text-3` | ink @ 45% | ink @ 45% | Placeholder, disabled | new |
| `--text-on-accent` | `#fff` | `#fff` | Labels on orange | c6 |

### Accent (the only action colour)

| Token | Light | Dark | Use | Source |
|---|---|---|---|---|
| `--accent` | `#ff5a1f` | `#ff5a1f` | Fills, icons, dots, active tab icon, NOW line | c6 `--acc` |
| `--accent-text` | `#c2410c` | `#ff8a5c` | Accent as text and links | new (contrast) |
| `--accent-strong` | `#c2410c` | `#c2410c` | Primary button fill, owner bubble, unread badge | new (contrast) |
| `--accent-sheen` | `#e2561a` | `#e2561a` | Top of the primary button gradient | c6 CTA gradient, darkened |
| `--accent-soft` | `rgba(255,90,31,.16)` | `rgba(255,106,43,.22)` | Accent chip and icon tile backgrounds | c6 `.tag` |
| `--accent-glow` | `0 10px 24px rgba(255,90,31,.35)` | same | Primary button shadow | c6 `.cta` |

**Decision:** c2 and c10 used `#ff6a2b`. c6 is the base, so `#ff5a1f` wins. White on `#ff5a1f` is only 3.1:1, so text and button fills use `#c2410c` (5.2:1).

### Status and stages

Chips use the `*-soft` fill with the strong colour as text.

| Token | Light text | Dark text | Soft fill | Meaning |
|---|---|---|---|---|
| `--ok` | `#15803d` | `#34d399` | `--ok-soft` | Booked, confirmed, delivered, done |
| `--warn` | `#b45309` | `#fbbf24` | `--warn-soft` | Quoted, unconfirmed, private note, stuck |
| `--danger` | `#b91c1c` | `#f87171` | `--danger-soft` | Missed call, failed, opted out, destructive |
| `--info` | `#1d4ed8` | `#60a5fa` | `--info-soft` | New |
| `--neutral` | ink @ 62% | ink @ 66% | `--neutral-soft` | Everything else |

Lead stages follow the API contract's nine statuses. The dot colours come from c8.

| Status (API value) | Shows as | Dot | Light text |
|---|---|---|---|
| `urgent` | **Urgent** as a red *flag* chip. The lead stays in its real column | `#ef4444` | `#b91c1c` |
| `new` | New | `#60a5fa` | `#1d4ed8` |
| `contacted` | Contacted | `#22d3ee` (new) | `#0e7490` |
| `quoted` | Quoted | `#fbbf24` | `#b45309` |
| `scheduled` | Booked | `#a78bfa` | `#6d28d9` |
| `closed` | Done | `#34d399` | `#15803d` |
| `collections` | To collect | `#f472b6` (c8 "Paid") | `#be185d` |
| `lost` | Lost (hidden behind a toggle) | `#94a3b8` | `#475569` |
| `garbage` | Junk (never on the board) | `#94a3b8` | `#475569` |

Appointment status: `pending` → "Not confirmed" (a Remy ghost slot on the calendar), `confirmed` → "Confirmed", `completed` → "Done", `cancelled` → "Called off" (struck through).
Call kind: `voicemail`, `missed_auto_texted` → "Missed · texted them back", `answered`, `outgoing`.

### Channels

These match the shipping apps, so a channel looks the same in the old and the new design.

| Channel | Colour | Gradient end |
|---|---|---|
| SMS | `#007aff` | `#0051d5` |
| WhatsApp | `#25d366` | `#128c7e` |
| Voicemail | `#a855f7` | `#6b21a8` |
| Web chat | `#6b7280` | `#374151` |
| Email | `#64748b` | `#334155` |

### Remy identity

| Token | Value | Use |
|---|---|---|
| `--remy-gradient` | conic `#ff6a2b → #ff5fa2 → #7b6cff → #3fd0ff` | The Remy mark (orb) only. c6 `.remyb` |
| `--remy-glow` | `0 0 18px rgba(255,95,162,.6)` | Orb glow |
| `--remy-tint` | violet @ 10% / 16% | Wash behind Remy-authored surfaces: suggestion card, Remy bubble |
| `--remy-edge` | violet @ 45% | 1.5px outline on Remy surfaces |

## Wallpapers

| Token | Where | Source |
|---|---|---|
| `--wall-vivid` | **Today only.** Five radial blobs (w1–w5) over a 160° gradient | c6 |
| `--wall-calm` + `--wall-calm-base` | **Every other screen.** Peach, lilac and sky blobs on `#eef1ff`; dark `#0c0e1d` with blobs at 35% | c10 |

**Decision:** c8's dark navy wall is dropped. Dark mode uses c6's dark blob colours (Today) and c10's dimmed calm wall (elsewhere). The wallpaper never animates (c6's 22s drift is dropped).

## Materials

| Token | Light | Dark | Notes |
|---|---|---|---|
| `--glass-fill` | white @ 34% | `rgba(30,32,48,.36)` | c6 `--g` |
| `--glass-fill-strong` | white @ 55% | `rgba(40,42,60,.55)` | c6 `--g2`: sheets, tab lens, selected segment |
| `--glass-blur` / `-saturate` / `-brightness` | 22px / 1.9 / 1.06 | same | c6. (c8 used 24/1.7, c10 22/1.8) |
| `--glass-edge` | white @ 75% | white @ 28% | 1px gradient rim, top-left and bottom-right (c6 `::before`) |
| `--glass-edge-2` | white @ 18% | white @ 5% | Inner bottom/side line |
| `--glass-highlight` | white @ 90% | white @ 35% | 1px inner top highlight |
| `--glass-shadow` | `0 18px 40px rgba(40,20,90,.18)` | `0 18px 40px rgba(0,0,0,.45)` | c6 `--sh` |
| `--frost-fill` | white @ 72% | `rgba(28,28,32,.66)` | c2 `--card`, blur 30, saturate 1.8, .5px border |
| `--surface` | white @ 94% | `rgba(22,24,36,.94)` | Solid content |
| `--surface-raised` | `#fff` | `#1c1f2e` | Bubbles, inputs |
| `--surface-sunken` | ink @ 5% | white @ 6% | Inputs, waveform track, grouped rows |
| `--line` / `--line-strong` | black @ 7% / 14% | white @ 8% / 16% | Hairlines (c2) |
| `--scrim` | black @ 18% | black @ 35% | c6 light, c8 dark |

**Reduce Transparency:** every fill becomes opaque (`#f4f5fa` / `#ffffff` light; `#1c1e2b` / `#262938` / `#161824` dark), all `backdrop-filter`s become `none`, rims become a plain 10% hairline, and the calm wallpaper is hidden.
**Increase Contrast:** `--text-2` goes to 82% (86% dark), hairlines to 22%, glass fill to 86%, and rims become a visible 35% border.

## Type

The system font is used on each platform (SF Pro on Apple; Roboto on Android; the same stack on web). Sizes are in rem in CSS (1rem = 16px at default text size) so the prototype can mimic Dynamic Type.

| Token | Size | Line | Weight | Tracking | Use | Source |
|---|---|---|---|---|---|---|
| `--t-hero` | 64 | .9 | 800 | -3 | The one big number on Today | c6 |
| `--t-display` | 40 | 1.02 | 800 | -1.4 | Today headline sentence | c6 h1 |
| `--t-large-title` | 30 | 1.1 | 800 | -0.8 | Tab root titles | c6, c8, iOS `screen` 30 |
| `--t-title` | 22 | 1.2 | 800 | -0.4 | Sheet titles, detail names | c6/c8/c10 |
| `--t-stat` | 28 | 1.0 | 800 | -1 | Stat tile numbers (tabular) | c6, iOS `statValue` 28 |
| `--t-headline` | 17 | 1.3 | 700 | 0 | Nav bar title, card titles | c2 |
| `--t-row-title` | 16 | 1.3 | 650/800 | 0 | Row names (800 when unread) | c6, iOS `rowName` 16 |
| `--t-body` | 16 | 1.45 | 450 | 0 | Messages, paragraphs | c6 15.5 → 16, iOS body 16 |
| `--t-subhead` | 14 | 1.35 | 550 | 0 | Previews, meta | c6 13.5 → 14 |
| `--t-footnote` | 13 | 1.3 | 600 | 0 | Small buttons, chips | c2 |
| `--t-caption` | 12.5 | 1.3 | 650 | 0 | Timestamps (tabular) | c6 |
| `--t-label` | 12 | 1.2 | 750 | +.5 upper | Panel labels ("TRANSCRIPT") | c6 `.k` |
| `--t-section` | 13 | 1.2 | 750 | +.7 upper | Section headings | c6 h3 |
| `--t-tag` | 12 | 1.0 | 750 | 0 | Chips, badges | c6 11 → 12 |
| `--t-tab` | 11 | 1.0 | 700 | 0 | Tab labels; stop growing at Large | c6 10.5 → 11, iOS 11 |

## Spacing, radii, elevation

- **Spacing:** a 4pt grid plus the half-steps the concepts use: 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40. Screen gutter **16** (c6). Content clears the floating tab bar by **130** (c6). Minimum hit target **44**.
- **Radii:** `xs 9` (grabber), `sm 12` (icon tile), `md 16` (event, input), `lg 20` (job card, banner), `xl 22` (row press, panel, action tile, bubble 22), `2xl 30` (cards; c2 was 28), `sheet 38` (c6; c8 34, c10 32), `pill 999`.
- **Elevation:** `e1` frosted card; `e2` glass chrome; `e3` floating tab bar (e2 + `0 20px 40px rgba(0,0,0,.12)`, c2); `drag` lifted job card (c8 `0 26px 50px rgba(0,0,0,.45)`); `accent-glow` primary button. Z-order: content 1, nav 10, tab bar 20, scrim 30, sheet 31, toast 50, drag 60.

## Motion

Names match the native `Motion` enum so the platforms stay aligned.

| Name | Web | iOS spring | Compose | Use |
|---|---|---|---|---|
| `signature` | 420ms `cubic-bezier(.2,.85,.25,1)` | response .253, damping .86 | ζ .86, k 620 | Push/pop, row → thread |
| `send` | 300ms `cubic-bezier(.3,1.5,.5,1)` | .238 / .62 | ζ .62, k 700 | Sent bubble grows from composer |
| `press` | 300ms `cubic-bezier(.3,1.5,.5,1)`, scale .97 | .209 / .72 | ζ .72, k 900 | Tap feedback |
| `lens` | 550ms `cubic-bezier(.3,1.5,.45,1)` | .35 / .75 | ζ .75, k 320 | Tab lens, segmented knob, toggle |
| `sheet` | 450ms `cubic-bezier(.2,1.05,.3,1)` | .42 / .9 | ζ .9, k 220 | Sheets |
| `rest` | 420ms `cubic-bezier(.2,.85,.25,1)` | .42 / 1.0 | ζ 1, k 220 | Anything structural |
| `tint` | 180ms ease-out | easeOut .18 | tween 180 | Colour only; never springs |
| `rise` | 600ms, 50ms stagger, first 6 items | stagger .016, first 8 rows | — | Content arriving |
| `remySpin` | 7s linear loop | — | — | Remy mark; off under Reduce Motion |
| `autoSendCountdown` | 5s | 5s | 5s | **Behaviour.** Never shorten |

**Decision:** c6 sheets used 0.5s with a 1.1 overshoot; c8 and c10 used 0.45s with 1.05. The spec uses 0.45s/1.05 because it is calmer. Press scale is .97 (c6 .965, c2 .975, c8 .97).

## Haptics

The names match native `Haptics`: **selection** (tab, segment, toggle, alphabet rail), **light** (button press, sheet open, card pick-up), **success** (sent, booked, approved), **warning** (failed send, consent refused, session expired). Web prototype: `navigator.vibrate(8)` after the first user gesture.
