Every control below is a CSS transcription of the real DS component it will be built from (DS Tabs with the sliding pill + connector flares, DS Select small, DS Bar, DS text Button rows, Paper elevations). Click a tab or a readout stat.
Everything below is decided. No new design calls needed to build it.
gto-hud-page.tsx (the GtoHudPanel with MAIN_STATS + AxisFooter), and the MAIN_DOMAIN shared axis with it.components/gto-hud-readout.tsx — the top block (variant chosen above).components/stat-tabs.tsx — the 10-stat selector.ByPositionPanel, VsMatrixPanel, VsActionPanel, FamilySeriesCards, LollipopRow, NoSpotTrack, row-layout.ts, StatDefinitionToggle/Body. Only their container changes.gtoHud.header.pageTitle stays "GTO stats" and the crumb carries "GTO HUD".| Mock element | Build with |
|---|---|
| Readout container | Paper elevation="3", rounded-md p-2.5, flex items-stretch gap-2 |
| Family box | div on bg-action-hover border border-divider rounded-sm + 2px absolute left edge in the family color |
| Readout stat row | Button color="secondary" variant="text" size="small" with data-tst="gto-hud-stat-{TYPE}"; selected = bg-primary-main/12 + ring-1 ring-primary-main/28 |
| Seat pod | Paper elevation="4" + rounded-full chip (bg-paper-elevation2 border-paper-elevation6, ring-2 ring-primary-main/12) |
| Stat tabs | Three ToggleButtonGroup size="small" (one per denominator family) in a flex gap-2 overflow-x-auto row — separate groups keep the sliding indicator intact and encode the grouping without captions |
| Tab label + value | Typography variant="body3" + variant="body4" color="text.ghost" |
| by-position & matrix row | flex flex-wrap gap-gutter items-start; by-position flex-1 basis-[460px] min-w-0, matrix shrink-0 |
| Panel titles | existing GtoHudPanel (subtitle2 + optional GTO column header) |
| Use | Token |
|---|---|
| Page / panel surfaces | paper.elevation2 page, elevation3 panels + readout, elevation4 by-position panel & seat pod, elevation5 borders |
| Family colors | hands dealt primary.main · unopened pots betting.range · facing an action betting.fold |
| Selected states | primary.states.focus (bg) + primary.main @28% (ring); hover action.hover |
| Text | value text.primary, label text.secondary, tab value text.ghost, scale hints text.disabled |
| F / C / R | betting.fold / betting.call / betting.raise.default |
| Radius | xxs family edge · xsm rows/cells · sm panels/boxes · med readout · full chip + bars |
| Spacing | spacing.gutter between blocks, spacing.panel inside panels |
| Type | h5 page title · subtitle2 panel titles · body3 values/labels · body4/cap keys and captions |
min-w-[136px], rows 22px with a 1px divider between them, seat pod w-[76px], chip 36px.basis-24 · track flex-1 min-w-[90px] · value w-10 text-right, row min-h-[38px] p-2.3.5rem label track + 4.25rem per aggressor, gap-0.25 (1px), cells h-8 rounded-xsm, impossible cells rendered empty.252px wide, chart 230×104, grid repeat(auto-fill,252px).| State | Treatment |
|---|---|
| Selected stat | Highlighted in both readout row and tab (single source of truth: selectedStat) |
| Hover | action.hover on readout rows and tabs; series card border paper.elevation8 |
| No spot | Hatched track + - in the value column + tooltip gtoHud.noSpot. Never 0. |
| Matrix impossible cell | Empty cell (reports convention for the diagonal) — no hatch at grid scale |
| Chart hover | DS ComposedChart per-card cursor + tooltip. No cross-card sync. |
flex-wrap: the readout drops under the title/solution block when it no longer fits (~lg).overflow-x-auto) — never wraps to two lines.md; matrix keeps its own overflow-x-auto.sm (basis-16 label, min-w-[50px] track, w-8 value); series cards go one per row.sm: switch to the single-strip layout (chips wrap) regardless of the desktop variant.MAIN_STATS; panels read STATS_BY_POSITION / VS_ACTION_TABLES / NBET_MATRICES. Statics stay until the BE endpoint lands (TODO(api) comment already in gto-hud-data.ts).gtoHud.readout.handsDealt ("of hands dealt"), gtoHud.readout.unopenedPots ("of unopened pots"), gtoHud.readout.facingAction ("facing an action"), and gtoHud.header.howTo ("How to use GTO HUD"). Add to en.json + descriptions.json only; never edit an existing value in place (the i18n guard rejects it).gametypes.groups.plo4, common.formatLabel.6max, gametypeLabelId(token), common.bb.3BET shows the matrix and the vs-table.aria-pressed / data attribute).- and not 0 (keep the existing assertion).pnpm nx run gto-wizard-web:type-check · test · oxlint · oxlint:cipnpm eslint apps/gto-wizard-web/src/pages/gto-hud --max-warnings 0pnpm nx run i18n:validate-i18n (descriptions.json must stay canonically sorted)node tools/scripts/ds-coverage.mjs --check — deleting the left column should reduce arbitraryPx; re-baseline only with a justification line in the commit