# DS audit — SURFACES · TYPOGRAPHY · READOUT/CARDS
Mock: `apps/artifacts/plo-gto-hud-redesign/v2.html` (v1 shares an identical `:root` + surface CSS — all findings apply to both). DS read from source; refs are `file:line`.

| # | Mock element (selector/line) | Mock value | DS truth (file:line) | Verdict | Exact fix |
|---|---|---|---|---|---|
| 1 | `.panel.e4` v2:119 (by-position/matrix/vs panels) | `paper4` | `GtoHudPanel` locks `!bg-paper-elevation3` gto-hud-panel.tsx:30; `VsActionPanel` `elevation="3"` vs-action-panel.tsx:124; `FamilySeriesCards` :59 | MISMATCH | Add an `elevation` prop to `GtoHudPanel` (drop `!bg-`) or use elevation3. Spec §1 "keep as-is" is wrong — elevation4 needs edits to all three panels. |
| 2 | `.panel` v2:118 | radius 6px | `rounded-md`=12px gto-hud-panel.tsx:30, vs-action-panel.tsx:122 (preset `md→radius.med` tailwind-preset.ts:52) | MISMATCH | `border-radius:12px`. |
| 3 | `.hud-row:hover` v2:91 / `.ds-btn-text:hover` v2:341 | `rgba(168,168,177,.08)` and `rgba(255,255,255,.08)` — two fills for one component | Button `text`+`secondary` hover = `rgba(224,224,224,.1)` colors.ts:515 / button.styles.ts:371, and hover forces `text-text-primary` :370 | MISMATCH | `rgba(224,224,224,.1)` + `color:#fff`. Add missing focus-visible `.3`, active `.16`, 1px white ring (`after:inset-[2px] rounded-[4px]` :24). |
| 4 | `.hud-row` v2:88 (rendered with `ds-btn-text` ⇒ a Button) | `padding:3px`, no height, radius 4px | Button `size=small` = `px-2 py-1 h-6` + `button/small` button.styles.ts:48-52; root `rounded-sm` :17 | HAND-ROLLED sizing | 3px is off-scale. Use `p-1` + explicit `h-auto min-h-[22px]` (cf. `min-h-[38px] p-2` lollipop-row.tsx:118). |
| 5 | `.hud` v2:81 | `paper3` + `1px solid var(--divider)` | Paper is elevation **or** border: `outlined` = `border-divider` **and forces `bg-paper-elevation0`** paper.styles.ts:7,42-43; default radius `rounded-sm` :38. "Depth via elevation, not borders" token-semantics.md:28-30 | HAND-ROLLED (hybrid Paper) | `<Paper elevation="3" className="rounded-md border border-divider p-2.5">`. Same hybrid in `.hud-box`/`.seat`/`.stat-chip`/`.vcard`. |
| 6 | `.hud` v2:82 | `padding:9px` | scale has 8 (`p-2`) / 10 (`p-2.5`) spacing.ts; spec §2 itself says `p-2.5` | NOT-A-TOKEN | `padding:10px`. |
| 7 | `.hud-box` v2:84, `.sf-col` v2:202, `.stat-chip` v2:188 | `--action-hover` as resting surface | `action.hover` is a **state layer** colors.ts:220-221, exposed "for component state layers" tailwind-preset.ts:84-87 | NOT-A-TOKEN (misuse) | `bg-paper-elevation4` `#1E1E21`. As written box fill = row hover fill, so hover barely registers. |
| 8 | `.vcard` v2:363 in `.panel.e4`; `.seat .chip` v2:99 in `.seat`(paper4) | `paper2` child on a `paper4` parent | Nested/active surfaces step **up**; higher = lighter token-semantics.md:28-35 | MISMATCH | Card → `bg-paper-elevation5` `#26262A` (or panel 3 / card 4). Chip → elevation5/6. |
| 9 | `.vcard` v2:363-365 | radius 6px, `padding:9px 10px 7px`, grid `gap:10px`, `minmax(200px,240px)` | shipped card `rounded-md border bg-paper-elevation2 p-2.5 pb-1.5`, `min-w-60 flex-1`, container `gap-3` family-series-cards.tsx:88,69 | MISMATCH + NOT-A-TOKEN | radius 12px; `padding:10px 10px 6px`; `gap:12px`. 9px/7px off-scale. |
| 10 | `.vcard.on`/`.card.on` v2:366,176 | `linear-gradient(180deg,rgba(170,251,178,.08),rgba(170,251,178,0) 62%)` | `bg-gradient-primary-vertical` = `.08 0% → .01 100%` gradients.ts:31-35, tailwind-preset.ts:372 | MISMATCH | Use the token gradient verbatim (end `.01` at `100%`). |
| 11 | `.vcard:hover` v2:365 | border only; `background:var(--paper2)` | The card **is** a text Button — `hover:bg-button-secondary-hover` still applies family-series-cards.tsx:86-92 | MISMATCH | Add the `rgba(224,224,224,.1)` hover fill, or spec `hover:bg-transparent` — otherwise the built card flashes a tint the mock never shows. |
| 12 | `--hatch` v2:35 | stripe only | `NoSpotTrack` = `backgroundColor rgba(255,255,255,.02)` **plus** the .05 stripe, `rounded-sm` no-spot-track.tsx:13-22 | MISMATCH | Add `background-color:rgba(255,255,255,.02)`. `.row .track .none` v2:138 radius 4px→6px. Delete `.vc-none`'s 1px `.05` border (not in the component). |
| 13 | `.hud-row + .hud-row` v2:90, `.v-sep` v2:186, `.tab-sep` v2:115 | `border-top:1px solid divider`, hand-built 1px spans | `Divider`: `bg-divider border-0`, vertical `w-[1px]`, `flexItem self-stretch`, `inset my-2` divider.styles.ts:4-21 | HAND-ROLLED | `<Divider/>` between rows (a `border-top` fights the Button focus ring); `<Divider orientation="vertical" flexItem inset/>` for `.v-sep` (inset = 8px, mock 4px). |
| 14 | `.hud-row.on` v2:92, `.stat-chip.on` :192, `.tab.on` :112 | `primary-focus` + `inset 0 0 0 1px rgba(170,251,178,.28)` / `.22` | Shipped selected row = `animatedGradientClassName` → `bg-gradient-primary-horizontal-animated bg-[length:200%]` animated-gradient-background-styles.ts:64-71, lollipop-row.tsx:119 | HAND-ROLLED + NOT-A-TOKEN | Reuse `animatedGradientClassName` so both row types share one selected state. `.22/.28` primary alphas exist nowhere (states are `.08/.12` colors.ts:116-117). |
| 15 | `.ds-bar` v2:345 / `.bar` v2:164 | `h 12px; radius 12px; bg --grey900` / `bg --paper0` | `Bar`: outer `h-4 rounded-xl bg-grey-800`, inner `size-full rounded-xl bg-grey-900`, segments `duration-300 ease-sharp` bar.tsx:49-50,93-94; page passes `h-3` vs-action-panel.tsx:110 | MISMATCH | `border-radius:24px`; `.bar` track must be `#424242`/`#212121`, not `--paper0`. Bar also ships per-segment Tooltips bar.tsx:71-84. |
| 16 | `title="…"` v2:560,785,858 | native tooltips | DS `Tooltip` wraps no-spot tracks/segments lollipop-row.tsx:80, vs-action-panel.tsx:105 | HAND-ROLLED | `<Tooltip content={<FormattedMessage id="gtoHud.noSpot"/>}>`. |
| 17 | `.q` v2:70 | 13px circle, `1px rgba(255,255,255,.22)`, 9px glyph | `HelpIconTooltip`: 12px icon (`size="xs"`), color `action.active` `rgba(168,168,177,.56)`, accepts a visible `label` help-icon-tooltip.tsx:53-70 | HAND-ROLLED | `<HelpIconTooltip size="xs" label content/>` for both "?" affordances. |
| 18 | `.field label` v2:73 | `t-body4cap` 10px/120%/500 caps, `gap:5px`, `mb:5px` | `Field` label = `input/label` 12px/100%/.15px/**400** + `gap-1 min-h-4 px-2`; root `gap-0.5` field.styles.ts:7-10, typography.styles.ts:20 | HAND-ROLLED + MISMATCH | `<Field label>` + `<Select size="small"/>`; 12px/400, `gap:4px`, root `gap-0.5`, `px-2`. |
| 19 | `.hud-row .v` v2:95, `.stat-chip .v` :194, `.seat .stack` :103 | `t-body3`/`t-body2` + `font-weight:600` | No such variant. 12px/600 = `subtitle3` (157%/.1px) typography.styles.ts:14; 14px/600 = `subtitle2` (140%/.1px) :13 | MISMATCH | `subtitle3` (readout values) / `subtitle2` (chips). Bolding a body variant silently changes tracking+leading. |
| 20 | `.seat .chip b` v2:102 | 10px/600/.17px | `body4` is 10px/143%/.17px/**500** typography.styles.ts:9 | MISMATCH | `font-weight:500`, or go `subtitle3` at 12px if bold is required. |
| 21 | `.hud-row .k` v2:557 | `t-body4cap` (10px caps) for stat keys | shipped row label = `variant="body3"` lollipop-row.tsx:65; this mock's own spec §2 says `body3` | MISMATCH (wrong variant) | `t-body3` for stat keys; keep `body4/cap` for captions only. |
| 22 | `.ph .gto` v2:121,800; `.vs-head .lab` :852 | `t-body4cap` (leading **120%**) | Components use `variant="body4"` + `uppercase` (leading **143%**) gto-hud-panel.tsx:45, vs-action-panel.tsx:150 | MISMATCH | `body4` + `uppercase`, or move the components to `body4/cap`. Pick one — the row shifts ~2px. |
| 23 | matrix cells v2:816,836 | primary alpha ramp `.12–.80`, ink `#111` | No heat token; on-primary ink = `primary.contrastText` `rgba(0,0,0,.87)` colors.ts:112 | NOT-A-TOKEN | Ink `rgba(0,0,0,.87)`; document the ramp as a page constant. |
| 24 | `GC.unopened` v2:536 / `D.groups` :534 | `var(--amber)` | Undefined in `:root`; family color is `--range` = `betting.range` `#FF8F00` colors.ts:246 | MISMATCH (broken) | `var(--range)`. Dead tokens: `--teal`, `--primary-dark`, `--action-selected`, `--grey800`, `--r-lg`, `--text-ghost`. |
| 25 | `.tabs-arrow:disabled` v2:124 | `rgba(255,255,255,.18)` | DS tabs use `text-text-disabled` = 22%, with 18% explicitly rejected tabs.styles.ts:223-225 | NOT-A-TOKEN | `rgba(255,255,255,.22)`. |
| 26 | `--r-xsm` v2:29 | `4px` | class is `rounded-xs` tailwind-preset.ts:50 | MATCH (value) | Note it as `rounded-xs`; `rounded-xsm` is silently invalid. |
| 27 | `.row`, `.vs-row` v2:129,166 | `border-radius:8px` | Tokens are 2/4/6/12/16/24 radius.ts:8-15; shipped also uses `rounded-[8px]` lollipop-row.tsx:118 | NOT-A-TOKEN (pre-existing) | Faithful to the app; either keep + note, or move both to `rounded-sm`. |
| 28 | `.vc-head` v2:369 | `margin-bottom:6px` | shipped `mb-0.5` = 2px family-series-cards.tsx:97 | MISMATCH (cosmetic) | `margin-bottom:2px`. |
| 29 | matrix grid v2:824 vs comment :145 vs spec §4 | `56px repeat(n,66px)` / "68px tracks" / "`4.25rem`" | — | contradiction | Pick `56px` + `68px` and fix the CSS. |
| 30 | `.cards`/`.card` v2:172-182 + spec §4 | dead v1 CSS at 252px, while §"what changed" says 240px cap and "196px column card" | — | contradiction | Delete `.cards`/`.card`; state one width (240px). |

**MATCH (verified exact):** all `:root` tokens — `--paper0..8` colors.ts:182-191, text :162-166, `--divider` :436, `--action-hover/selected` :220-222 (`neutralGrey` `rgb(168,168,177)` :14), primary+states :110-118, betting :242-248, `--teal` :299, `--states-*` :168-172, `--input-fill*` :446-449, greys :82-83, radii radius.ts:8-15, `--gutter/--panel` spacing.ts:59-60. All `.t-*` classes byte-exact vs typography.styles.ts:7-45 (`font-heading`=Poppins tailwind-preset.ts:179-184). `.ds-select` = Select `size="small"` select.styles.ts:6-35. `POS_COLOR` = `colorDots.*` :295-304. Row geometry, tick `w-0.25 bg-paper-elevation5`, dot `size-3 ring-2 ring-paper-elevation3`, stem `bg-primary-main/60`, `.vh i` `size-1.5` — row-layout.ts:8-10, lollipop-row.tsx:73-92. Tabs indicator/panel `paper3` + `rounded-b-[12px]` tabs.styles.ts:320-354.

## (a) DS components/props the mock should use but doesn't
`Paper` with `elevation` (+ an explicit elevation-vs-border decision, since `outlined` flattens to elevation0) · `Button color="secondary" variant="text" size="small"` with its real state layers and `h-6 px-2 py-1` · `Typography` `subtitle3`/`subtitle2` instead of hand-bolded body, `body3` for stat keys, `input/label` for labels · `Divider` · `Tooltip` · `HelpIconTooltip` · `Field` + `Select size="small"` · `Bar` + `NoSpotTrack` · `GtoHudPanel` — needs a real `elevation` prop before this mock's elevation4 panels are buildable.

## (b) Values not traceable to a token
`9px` (`.hud`), `9px/7px` (`.vcard`), `5px` (`.hud-box`/`.sf-col`/field gaps), `3px` (`.hud-row`), `rgba(170,251,178,.22/.28/.35)` (`.35` matches shipped `border-primary-main/35`), matrix `.12→.80` ramp, `#111` ink, `rgba(255,255,255,.18)`, `.05` as a border on `.vc-none`, `8px` row radius, `22px` `.vc-bar` cap, layout literals `136/74/76/36/66/252px`, undefined `var(--amber)`.

## (c) Things the DS genuinely cannot express (custom component needed)
1. **Column chart** `.vc-plot/.vc-col/.vc-bar` — `ComposedChart` has no bar/column mark (line/area/dots only).
2. **2px family-color accent rail** (`.hud-box::before`, `.stat-chip::before`) — no token/component for a categorical rail on a surface.
3. **Stat tile** `.stat-chip` — DS `Chip` is a pill (`rounded-[100px]`, `chip/label` 12px/400, stacked sublabel) chip.styles.ts:22,32; it cannot produce a 6px rail tile.
4. **Seat "GTO" avatar** — no Avatar in `libs/ui`; Paper + `rounded-full` + ring is the honest transcription.
5. **Per-position palette** — no `POSITION_COLORS` exists; `SERIES_COLORS` is per-stat (gto-hud-meta.ts:30-39) and `colorDots` is the *user-assignable tag* palette (colors.ts:273-275). The mock's "same palette as the app's by-position legend" is not true today — needs a new shared constant.
6. **Matrix heat scale** — no ramp tokens; must live as a documented page constant.
