# Craft — how it looks, as opposed to what it is made of

> **This sheet is judgment, not law.** It changes with a design round and needs no
> ledger entry. Split out of `design/tokens.md` on 2026-07-31 (operator, grilled —
> `docs/DECISIONS.md`) because five design calls in one round collided with a file
> that had frozen taste decisions behind "changes need an operator round".
>
> `design/tokens.md` still owns the things with a right answer: colour, the 4/8
> spacing grid, radii, breakpoints, contrast floors, channel colours, assets. A
> wrong value there is a bug. A disagreement here is a design conversation.
>
> **There is no oracle on this file.** That is deliberate. See casing.

## Type

| token | value | used for |
|---|---|---|
| `3xs` | 10px | micro annotations only — slider ticks, dense foot lines |
| `xs` | 12px | **the label size** — field labels, column headers, meta, section eyebrows |
| `sm` | 13px | body, table cells, stat-tile labels |
| `md` | 15px | row names, list titles, the name of the thing a card is about |
| `lg` | 18px | section headings |
| `xl` | 24px | sub-page titles, secondary figures |
| `2xl` | 30px | **page titles and hero figures only** |

Weights: `400` body · `500` labels and nav · `600` emphasis · `650` headings ·
`700` page titles and hero figures only.

`md` and `lg` were near-unused before round 4, which is why screens flattened into
a wall of 13px. If a screen has no 15px and no 18px on it, it is probably flat.

**Numerals.** `tabular-nums` in columns that must align vertically — table rows,
axis ticks, a stack of figures on one spine. **Proportional everywhere else**,
including standalone stat values and hero figures: tabular gives every digit the
width of a zero, which makes a large number look loose.

## Casing

**Sentence case. Uppercase is kept where it is appropriate, and there is no
enforcement with or against it** (operator, 2026-07-31, verbatim).

What that means in practice: `tokens.md` used to *require* 11px uppercase for
every field label, column header, chip and stat label. That mandate is gone. It
has not been replaced by a ban, and no grep gates it. Each use is a judgment.

Two uses survived the round-4 sweep, and they are the shape of the exception:
`.cm-brand__sub` (a brand mark, not a label) and `.cm-note__label` (a deliberate
"Internal note" warning marker on a warn fill). Both earn it by not being labels.

**Stated plainly because it is a trade the operator made knowingly:** this removes
the mandate that produced the problem and adds nothing that prevents its return.

## Component chrome

- **Card**: `surface`, 1px `line`, radius 6, padding 16.
- **Status pill**: solid fill in the state's colour, white text, 11px/650, radius
  3, letter-spacing 0.02em. One per card at most. It is the only filled,
  text-bearing element on a card — if something else is filled and carries text,
  they compete and the pill loses.
- **Channel identity**: a small brand mark (22px, radius 5) plus the name in ink.
  Not a filled chip, for the reason above.
- **Input/field**: `surface`, 1px `line-strong`, radius 4, padding 10/12; hover
  border `accent`; focus ring 2px `accent-ink` at offset 2.
- **Segmented control**: bordered group, radius 4; active segment `accent` fill
  with `accent-contrast` text.
- **Table**: header row of 12px/500 sentence-case labels, 1px hairline rows, row
  hover `hover`, selected row `accent-soft` with a 2px inset `accent` bar, cells
  13px, numbers tabular.
- **Nav item**: 13px/500, radius 4; active is `accent-soft` + `accent-ink` at 600.
- **Stat tile**: **label above the figure**, 13px/500 sentence case; figure 26–30px
  at 600–700, proportional figures. You read what it is, then how much.
- **Focus**: a visible 2px `accent-ink` outline on every interactive element. This
  one is not judgment — it is an accessibility floor and it lives here only
  because the rest of the chrome does.
- **Empty values**: the em dash is the null placeholder in a data cell, and that
  is the only place the character appears anywhere in the product.

## Width and density

- Shell chrome: a 56px icon rail, plus a 196px sub-nav panel on an area with
  more than one screen (operator ruling 2026-08-04). It replaced the 216px sidebar.
- **No global content cap.** The 1360px maximum was removed on 2026-07-31: a
  dashboard of cards wants the display it is given. A screen that is a single
  column of prose or one table should still cap itself, by its own judgment.
- Row padding 9px, card padding 16px, gap 16px, section gap 26px, page padding 28px.
- Columns come from `auto-fit` grids, never from per-screen viewport breakpoints.
  The only media query a screen should own is `--cm-bp-shell`.

## Copy

- Every user-visible string passes the `edit-prose` detector. Run it on the
  extracted strings, do not eyeball it.
- **No em dashes in prose.** The glyph is the null placeholder and nothing else.
  Middots (·) separate inline meta.
- Status and outcome labels are words, never codes.
- No question-phrased headings.
- Arabic content renders with `dir` handling; translations sit on their own LTR
  line under the original.

## The trend caption

A caption under a sparkline is a stated rule or it is blank. The slot keeps its
height either way so cards stay aligned.

| caption | fires when |
|---|---|
| Most in two weeks | `today > max(previous 13 days)` |
| Fewest in two weeks | `today < min(previous 13 days) && today > 0` |
| Nothing for N days | `today === 0 && the last N days are all 0` |
| None answered | `inbound > 0 && outbound === 0` |
| *(blank)* | everything else |

A vague trend claim — "drifting down all fortnight" — is worse than silence,
because nobody can check it.
