# Postie — TOKENS

The printable mirror of `tokens/`. Every token in text. Namespace `--postie-`. Entry point
`styles.css` (eight `@import` lines, in order: fonts → colors → themes → typography → spacing →
shape → motion → base). Brand `brand-001-postie` · colourway 4, charcoal + oxblood · dark default,
light alternate · P3 step 5, formalised 2026-09-20 after the green-light (2026-09-20T20:57:01Z).

Contrast is WCAG relative luminance, computed. Text floor **4.5:1**; UI edges, large display and
non-text shapes **3:1**. Print values are starting matches — **always proof before a run** — and
in this pack they are **reference only: Postie has no print surface** (`guidelines/print-spec.card.html`).

---

## 1. Palette — 31 fixed values (`tokens/colors.css`)

Values never change per theme; only roles swap (§5). CMYK is the standard sRGB → CMYK conversion
(K = 1 − max(R,G,B)); Pantone is the nearest **solid coated** by CIE76 ΔE from a compact reference
table (`phase-3/foundations/verify/pantone-table.json`). Never build the dark grounds from rich
black — use the named spot or the listed 4-colour build with total ink ≤ 260%. Near-whites are
unprinted stock / knock-outs.

### Grounds

| Token | Hex | RGB | CMYK | Nearest Pantone C (ΔE) | Job |
|---|---|---|---|---|---|
| `--postie-charcoal` | `#1F1D1E` | 31 29 30 | 0 6 3 88 | 419 C (3.5) | brand ground — chrome-bar, masthead-band, hero, agent card, brand-tone buttons; both themes |
| `--postie-charcoal-deep` | `#141213` | 20 18 19 | 0 10 5 92 | Black 6 C (7.1) | raised on the chrome, dark — search well, avatar, chips |
| `--postie-charcoal-lift` | `#282627` | 40 38 39 | 0 5 2 84 | 426 C (2.5) | raised on the chrome, light — a lighter step of the chrome |
| `--postie-night` | `#111011` | 17 16 17 | 0 6 0 93 | Black 6 C (7.2) | working ground, dark — list, message, tables |
| `--postie-night-raised` | `#1A1819` | 26 24 25 | 0 8 4 90 | 419 C (5.6) | rail, panels, buttons, bottom bar |
| `--postie-night-selected` | `#231B1D` | 35 27 29 | 0 23 17 86 | 419 C (6.2) | selected row · triage-band tint (dark) |
| `--postie-night-rule` | `#2A2526` | 42 37 38 | 0 12 10 84 | Black C (3.3) | decorative separator, dense tables only |
| `--postie-night-edge` | `#6E6566` | 110 101 102 | 0 8 7 57 | 410 C (5.1) | the only visible UI border on night — never text |
| `--postie-night-pebble-1` | `#2F292A` | 47 41 42 | 0 13 11 82 | Black C (2.9) | account-pebble step 1 |
| `--postie-night-pebble-2` | `#3D3536` | 61 53 54 | 0 13 11 76 | 440 C (3.4) | account-pebble step 2 |
| `--postie-paper` | `#FBFAFA` | 251 250 250 | 0 0 0 2 | unprinted stock | working ground, light — list, message, tables |
| `--postie-paper-raised` | `#EEEDED` | 238 237 237 | 0 0 0 7 | 9180 C (2.7) | rail, panels, buttons, bottom bar |
| `--postie-paper-selected` | `#F2DCDF` | 242 220 223 | 0 9 8 5 | 7422 C (2.4) | selected row — an oxblood tint |
| `--postie-paper-rule` | `#E4E2E2` | 228 226 226 | 0 1 1 11 | 663 C (2.7) | decorative separator, dense tables only |
| `--postie-paper-edge` | `#8A8788` | 138 135 136 | 0 2 1 46 | Cool Gray 8 C (2.6) | the only visible UI border on paper — never text |
| `--postie-paper-pebble-1` | `#DAD8D8` | 218 216 216 | 0 1 1 15 | Cool Gray 1 C (1.7) | account-pebble step 1 |
| `--postie-paper-pebble-2` | `#C7C4C5` | 199 196 197 | 0 2 1 22 | Cool Gray 3 C (2.6) | account-pebble step 2 |

### Inks — three solid steps per ground

| Token | Hex | RGB | CMYK | Nearest Pantone C (ΔE) | Job |
|---|---|---|---|---|---|
| `--postie-ink-on-charcoal` | `#F1ECEC` | 241 236 236 | 0 2 2 5 | 9180 C (2.7) — knock out | text on charcoal / charcoal-deep / charcoal-lift; pebble ink on night pebbles |
| `--postie-body-on-charcoal` | `#C7BEBF` | 199 190 191 | 0 5 4 22 | 5235 C (4.1) | body on the chrome |
| `--postie-muted-on-charcoal` | `#978C8D` | 151 140 141 | 0 7 7 41 | 409 C (2.4) | muted / eyebrow / key-hint on the chrome; the ghost-button edge there |
| `--postie-ink-on-night` | `#ECE7E7` | 236 231 231 | 0 2 2 7 | 9180 C (3.2) — knock out | text on night / night-raised / night-selected |
| `--postie-body-on-night` | `#BBB2B3` | 187 178 179 | 0 5 4 27 | Cool Gray 5 C (4.2) | body on night |
| `--postie-muted-on-night` | `#8F8586` | 143 133 134 | 0 7 6 44 | 409 C (2.0) | muted on night |
| `--postie-ink-on-paper` | `#141314` | 20 19 20 | 0 5 0 92 | Black 6 C (6.8) | text on paper / paper-raised / paper-selected; pebble ink on paper pebbles |
| `--postie-body-on-paper` | `#4A4849` | 74 72 73 | 0 3 1 71 | 7540 C (4.4) | body on paper |
| `--postie-muted-on-paper` | `#646162` | 100 97 98 | 0 3 2 61 | Cool Gray 10 C (3.4) | muted on paper |

### The one signal — oxblood, ≈5% of any view

| Token | Hex | RGB | CMYK | Nearest Pantone C (ΔE) | Job |
|---|---|---|---|---|---|
| `--postie-oxblood` | `#8A1E2B` | 138 30 43 | 0 78 69 46 | 7427 C (5.7) | FILL on every ground (primary button, voice-pill, All chip, live dot; triage-band on light); accent-as-TEXT on paper only |
| `--postie-ink-on-oxblood` | `#F6EDED` | 246 237 237 | 0 4 4 4 | 9060 C (3.2) — knock out | the only ink on the oxblood fill |
| `--postie-oxblood-lift` | `#E4606C` | 228 96 108 | 0 58 53 11 | 709 C (6.9) — screen only | accent-as-TEXT on charcoal and night: link, count, unread-dot, triage signal |
| `--postie-oxblood-lift-hover` | `#EE7A85` | 238 122 133 | 0 49 44 7 | 1775 C (9.0) — screen only | hover of the lifted face |
| `--postie-oxblood-deep` | `#6F1622` | 111 22 34 | 0 80 69 56 | 1815 C (5.5) — screen only | hover of oxblood-as-text and of the primary fill, on paper |

---

## 2. On-colour inks (the gate reads these — `/* on: */` in `tokens/colors.css`)

| Ink | Hex | on | Ratio |
|---|---|---|---|
| `ink-on-charcoal` | `#F1ECEC` | `charcoal` | 14.33 |
| `body-on-charcoal` | `#C7BEBF` | `charcoal` | 9.22 |
| `muted-on-charcoal` | `#978C8D` | `charcoal-lift` (its lowest legal ground) | 4.62 |
| `ink-on-night` | `#ECE7E7` | `night` | 15.51 |
| `body-on-night` | `#BBB2B3` | `night` | 9.16 |
| `muted-on-night` | `#8F8586` | `night-selected` (its lowest legal ground) | 4.71 |
| `ink-on-paper` | `#141314` | `paper` | 17.79 |
| `body-on-paper` | `#4A4849` | `paper` | 8.71 |
| `muted-on-paper` | `#646162` | `paper-selected` (its lowest legal ground) | 4.69 |
| `ink-on-oxblood` | `#F6EDED` | `oxblood` | 7.93 |
| `oxblood-lift` | `#E4606C` | `charcoal` | 4.94 |
| `oxblood-lift-hover` | `#EE7A85` | `charcoal` | 6.19 |
| `oxblood-deep` | `#6F1622` | `paper` | 11.11 |

## 3. Every legal pairing, with its ratio (`guidelines/colors-pairings.card.html`)

**On charcoal `#1F1D1E`** — ink 14.33 · body 9.22 · muted 5.15 · oxblood-lift 4.94 · oxblood-lift-hover 6.19 · ghost edge (muted-on-charcoal) 5.15
**On charcoal-deep `#141213`** — ink 15.95 · body 10.26 · muted 5.73 · oxblood-lift 5.50
**On charcoal-lift `#282627`** — ink 12.85 · body 8.27 · muted 4.62 · oxblood-lift 4.43 *(icon stroke / non-text only — below the 4.5 text floor, clears 3:1)*
**On night-pebble-1 / -2** — ink-on-charcoal 12.20 / 10.19
**On night `#111011`** — ink 15.51 · body 9.16 · muted 5.30 · oxblood-lift 5.60 · oxblood-lift-hover 7.01 · edge 3.36 (UI)
**On night-raised `#1A1819`** — ink 14.43 · body 8.52 · muted 4.94 · oxblood-lift 5.21 · edge 3.13 (UI)
**On night-selected `#231B1D`** — ink 13.76 · body 8.13 · muted 4.71 · oxblood-lift 4.97
**On oxblood `#8A1E2B`** — ink-on-oxblood 7.93
**On paper `#FBFAFA`** — ink 17.79 · body 8.71 · muted 5.88 · oxblood 8.76 · oxblood-deep 11.11 · edge 3.42 (UI) · charcoal vs paper 16.09 (non-text)
**On paper-raised `#EEEDED`** — ink 15.86 · body 7.77 · muted 5.24 · oxblood 7.81 · edge 3.05 (UI)
**On paper-selected `#F2DCDF`** — ink 14.19 · body 6.95 · muted 4.69 · oxblood 6.99
**On paper-pebble-1 / -2** — ink-on-paper 13.06 / 10.71

Lowest legal text pairings: 4.62 (muted on charcoal-lift) · 4.69 (muted on paper-selected) · 4.71 (muted on night-selected).

## 4. Illegal — recorded so nobody re-tries them

| Fore | Ground | Ratio | Rule |
|---|---|---|---|
| oxblood `#8A1E2B` | night · charcoal · charcoal-lift | 2.08 · 1.84 · 1.65 | oxblood is never text on any charcoal — use oxblood-lift |
| oxblood-lift `#E4606C` | paper · paper-selected | 3.26 · 2.60 | the lifted face is never text on paper — on paper the accent-as-text face IS oxblood |
| oxblood-lift-hover `#EE7A85` | paper | 2.60 | never on paper — hover on paper is oxblood-deep |
| ink-on-paper `#141314` | oxblood | 2.03 | charcoal ink never sits on the oxblood fill — always ink-on-oxblood |
| muted-on-charcoal `#978C8D` | paper-selected · paper | 2.49 · 3.12 | the two muted faces are not interchangeable |
| night-edge · paper-edge | as text | 3.36 · 3.42 | an edge is an edge (3:1 class), never text |
| oxblood fill silhouette | night · charcoal | 2.08 · 1.84 | accepted non-text case: the primary button is read by its light ink and pill shape; the fill has no edge on any ground |

## 5. Theme mapping (`tokens/themes.css`)

Dark is the default (`:root` and `[data-theme="dark"]`, `color-scheme: dark`); light is the
alternate (`[data-theme="light"]`). **Every scope applies `color` + `background-color`** — never
a remap alone. Brand-ground roles are theme-invariant (dark chrome over paper); working-ground
roles invert.

| Role token | Dark (default) | Light (alternate) |
|---|---|---|
| `--postie-ground` | charcoal `#1F1D1E` | charcoal `#1F1D1E` |
| `--postie-raised-brand` | charcoal-deep `#141213` | charcoal-lift `#282627` |
| `--postie-ink-brand` · `body-brand` · `muted-brand` | `#F1ECEC` · `#C7BEBF` · `#978C8D` | same |
| `--postie-accent-text-brand` · `accent-hover-brand` | `#E4606C` · `#EE7A85` | same |
| `--postie-edge-brand` | muted-on-charcoal `#978C8D` | same |
| `--postie-surface` · `raised` · `selected` | `#111011` · `#1A1819` · `#231B1D` | `#FBFAFA` · `#EEEDED` · `#F2DCDF` |
| `--postie-rule` · `edge` | `#2A2526` · `#6E6566` | `#E4E2E2` · `#8A8788` |
| `--postie-pebble-1` · `pebble-2` · `pebble-ink` | `#2F292A` · `#3D3536` · `#F1ECEC` | `#DAD8D8` · `#C7C4C5` · `#141314` |
| `--postie-ink` · `body` · `muted` | `#ECE7E7` · `#BBB2B3` · `#8F8586` | `#141314` · `#4A4849` · `#646162` |
| `--postie-accent` · `accent-ink` | oxblood `#8A1E2B` · `#F6EDED` (FILL only) | oxblood `#8A1E2B` · `#F6EDED` (FILL for the primary moments) |
| `--postie-accent-text` · `accent-hover` | `#E4606C` · `#EE7A85` | `#8A1E2B` · `#6F1622` |
| `--postie-triage` · `triage-ink` · `triage-signal` | `#231B1D` · `#ECE7E7` · `#E4606C` (tint) | `#8A1E2B` · `#F6EDED` · `#F6EDED` (fill) |
| `--postie-select-fill` · `select-ink` | `#1F1D1E` · `#F1ECEC` | `#8A1E2B` · `#F6EDED` |

**The context layer** `--postie-c-*` (ground · raised · ink · body · muted · edge · accent-text ·
accent-hover · pebble-1 · pebble-2 · pebble-ink · selected · select-fill · select-ink) is what
components read. The theme scope sets it to the working roles; `.postie-brand` sets it to the
brand roles (pebbles = one raised-brand step; select-fill = oxblood in both themes);
`.postie-working` re-enters the working roles inside a brand block. The nearest ancestor wins by
inheritance — a component never writes a `.postie-brand <x>` descendant rule.

## 6. Where alpha is legal

**None.** No `rgba()`, no `hsla()`, no `#RRGGBBAA`, no `opacity` anywhere in the token layer or
on any text. The ghost-button edge is a solid inset box-shadow in a solid edge token.
De-emphasis steps down the ramp (ink → body → muted); separation is a tonal step of the ground.

## 7. Type — `nunito-pair` (`tokens/fonts.css` · `tokens/typography.css`)

| Token | Value |
|---|---|
| `--postie-font-display` | `'Nunito', 'Nunito Sans', 'Helvetica Neue', Arial, sans-serif` |
| `--postie-font-ui` | `'Nunito Sans', 'Nunito', 'Helvetica Neue', Arial, sans-serif` |
| `--postie-font-mono` | `ui-monospace, Menlo, Consolas, monospace` |
| `--postie-weight-display` | `800` — the only display weight |
| `--postie-tracking-display` | `-0.03em` (20–60px) · `--postie-tracking-display-xl` `-0.04em` above 60px · `-0.02em` at pane size |
| `--postie-tracking-label` | `0.14em` — uppercase labels, 11–13px, 700 |
| Display scale (recorded from the components; 1.25 × 4 off 16px, on the pixel grid) | `--postie-display-1` 40 · `-2` 32 · `-3` 24 · `-4` 20 (panel title 22) |
| Body + UI (Nunito Sans 400 / 600 / 700, line-height 1.5) | `--postie-text-lg` 16 · `-md` 15 · `-base` 14 · `-sm` 13 · `-xs` 12 · `--postie-label` 11 |
| Mono | `--postie-mono` 13 |

Display never sets running text. Tabular lining figures are on by default (`base.css`).

## 8. Spacing (`tokens/spacing.css`) — packed, base 4

`--postie-space-1` 4 · `-2` 8 · `-3` 12 · `-4` 16 · `-5` 20 · `-6` 24 · `--postie-gap-section` 32.
Padding scale 8 / 12 / 16. Leading 1.5, measure 65–75ch, 16px base. Marketing surfaces may loosen
one step (12 / 16 / 24, sections 64–96) with the same tokens.

## 9. Shape (`tokens/shape.css`)

`--postie-radius-row` 12 (rows, tiles, wells) · `--postie-radius-panel` 16 (panels, cards) ·
`--postie-radius-pill` 999 (every button, chip, pebble, input) · `--postie-edge-width` 1.5px.
Crisp, machined; no shadow, no glow. Decoration: masthead band on small surfaces (390px phone,
600px email), quadrant on large (the brand ground frames a working card). Derived from the
container, never a fixed pixel. Signature shape: the pebble; no pigeon mark rendered — type carries it.

## 10. Motion (`tokens/motion.css`)

`--postie-duration` 180ms · `--postie-ease` `cubic-bezier(0.2, 0, 0, 1)`. Stagger = one duration
step per row. Under `prefers-reduced-motion: reduce` the duration is 0ms — everything suspended.
Text never moves after load; figures never animate.

## 11. The brand's own rules (`components/RULES.md`)

- **`ground-role-split`** — charcoal is the brand ground in both themes; the working ground is night on dark and paper on light. Never a saturated colour behind dense text; never a working surface tinted to match the chrome.
- **`tonal-step`** — separation is a change of ground (surface → raised → selected) or space. No rules, no shadows, no borders as separators; `rule` is decorative and legal only inside dense tables.
- **`edge`** — one visible UI border, 1.5px, in the edge token on the working ground and muted-brand on the chrome. It clears 3:1 and is never text. The oxblood fill has no edge on any ground.
- **`nunito-pair`** — Nunito 800 for the wordmark, titles, figures and subjects, tracked −0.03em; Nunito Sans 400 / 600 / 700 for everything else, tabular figures on. Display never sets running text.

Colour discipline: oxblood is never text on any charcoal (use the lift); the lift is never text on
paper (use oxblood); charcoal ink never sits on oxblood; nothing sets body text in the accent; no
status hue exists — state is word + dot; no opacity on text.

## 12. What changed vs the working set

**Nothing.** Proven in headless Chromium (`phase-3/foundations/verify/equivalence-report.txt`):
every one of the working set's 61 `--postie-*` custom properties, plus the applied `color`,
`background-color`, `font-family`, `font-variant-numeric` and `color-scheme`, computed in 19
scopes (root, both `[data-theme]` sections, `.postie-brand`, `.postie-working` and their nestings)
under five theme / reduced-motion states — 6,270 comparisons, **0 differences**. Button, MailList
and the app kit (dark + light) re-rendered off `styles.css` are **pixel-identical** to their
`tokens.working.css` renders (`pixel-report.txt`). The canonical layer adds 43 named aliases
(the palette names in §1 and the recorded type scale in §7) that resolve to the same values; no
consumer reads them yet and no working-set name or value moved.

Two documentation notes for the record, not value changes: `phase-2/pairings.md` lists muted on
paper-raised as 4.72 — the computed ratio is 5.24 (`#646162` on `#EEEDED`); and `oxblood-lift`
on `charcoal-lift` measures 4.43, below the 4.5 text floor, so that pairing is recorded here as
icon-stroke / non-text only (the components use it only as an icon stroke).
