v1v2 — compact columns

GTO HUD — readout + tabs (v2)

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.

mock chrome — not part of the build Hold'emPLO4PLO5 PlayStudyPracticeAnalyze
GTO HUD ?How to use GTO HUD
PLO4 · 6-max · PLO500 · 100bb
GTO values only — solver-simulated hands. Hatched track = no spot at that position.

What changed in v2

DS audit — 69 findings, resolved

Three independent passes over this mock against libs/ui (tabs + select · surfaces/typography/readout · data-viz + tables). Raw reports: tabs · surfaces · data-viz.

Fixed in this mock

WasNow (DS truth)
Tab padding 0 16px6px 16px 9px — DS pt-[6px] pb-[9px], labels were 1.5px low
Tab list rebuilt on every clickBuilt once; only aria-selected/tabindex change, so the indicator actually slides (200ms) and focus survives
Both panel corners flashed round on switchAsymmetric like Tabs.Panel: losing a flush edge is immediate, gaining one defers to the indicator's transitionend (400ms fallback)
Select: hover only, min-w 260px, text glyph chevronfocus-visible + data-popup-open (1px primary-main border), w-auto min-w-56, truncating value, 16px ChevronDownIcon
Field label as body4/cap, 13px help circleinput/label 12px/100%/.15px/400, gap-1 px-2, root gap-0.5, 12px help icon at action.active
Hover fill states-hover on rows/cardsThe Button's own colors.button.text.secondary.hover = rgba(224,224,224,.1), plus focus/active layers
Selected row = invented /12 bg + /28 ringgradients.primary.horizontal(.08,.01); cards use the bg-gradient-primary-vertical token (.08→.01 full height)
Panels: 6px radius, nested same-elevation Papers, bg-action-hover as a resting surfaceSections are not Papers — Divider-separated blocks on the Tabs.Panel (elevation3); cards/boxes elevation4, seat chip elevation5, radius rounded-md=12
Hand-bolded body3/body2 valuessubtitle3 / subtitle2; uppercase keys are body4+uppercase (143%), not body4/cap (120%)
Matrix 56px/66px tracks, 4px cells, #111 ink, 2px ramp49px/59.5px (= 3.5rem/4.25rem at the app's 14px root), rounded-sm=6 cells, grey-900 ink, ramp rounded-sm + gap-1
rounded-xl treated as 12px24px in this preset (tailwind-preset.ts); DS Bar corrected
No-spot = bare stripes + invented borderNoSpotTrack: alpha(text.primary,.02) base + .05 stripes, no border
Column bars had a 2px floorNo floor — a real 0.0 draws nothing, so zero can never look like a small value
Seat colours used as 10px label textHue on bars/dots only; labels text.secondary (CO 3.1:1 and BTN 4.0:1 failed AA)
Disabled/focus states missing on tabs + arrows; no rolesaria-disabled gating, focus rings, role=tablist/tab/tabpanel, aria-controls/labelledby, roving tabindex, Arrow/Home/End
Dead hand-rolled pill CSS, stale spec rows, var(--amber) undefinedDeleted / corrected (--range), spec's "three ToggleButtonGroups" replaced with the real Tabs.Root props

Deliberate deviations — the DS cannot express these

Decisions — all closed

Implementation spec

Everything below is decided. No new design calls needed to build it.

1. What changes in the app

2. Component map

Mock elementBuild with
Readout containerPaper elevation="3", rounded-md p-2.5, flex items-stretch gap-2
Family boxdiv on bg-action-hover border border-divider rounded-sm + 2px absolute left edge in the family color
Readout stat rowButton color="secondary" variant="text" size="small" with data-tst="gto-hud-stat-{TYPE}". Hover fill is the Button's own colors.button.text.secondary.hover = rgba(224,224,224,.1) (NOT states-hover), and it also forces text→text-primary. Selected = the app's animatedGradientClassName() surface (gradients.primary.horizontal(.08,.01)) — no invented ring alpha.
Selected card tintbg-gradient-primary-vertical (gradients.primary.vertical() = .08→.01 over the full height). Do not hand-write a to-transparent to-60% gradient.
Row separators / hairlinesDS Divider (1px colors.divider), not ad-hoc borders
Value typographysubtitle3 (12px/157%/.1px/600) for readout values, subtitle2 for the strip variant — do not bold a body* variant by hand. Uppercase stat keys are body4 + uppercase (143%), not the body4/cap variant (120%).
Help icon / field labelDS HelpIconTooltip (12px icon at action.active) inside Field.Root/Field.Label (input/label 12px/100%/.15px/400, gap-1, px-2, root gap-0.5); the label turns primary-main while the dropdown is open
TooltipsDS Tooltip / Bar's config[].label. The mock uses native title= only because it is a static file — never ship that.
Seat podPaper elevation="4" + rounded-full chip (bg-paper-elevation2 border-paper-elevation6, ring-2 ring-primary-main/12)
Stat tabsThree 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 + valueTypography variant="body3" + variant="body4" color="text.ghost"
by-position & matrix rowflex flex-wrap gap-gutter items-start; by-position flex-1 basis-[460px] min-w-0, matrix shrink-0
Panel titlesexisting GtoHudPanel (subtitle2 + optional GTO column header)

3. Tokens (already verified against libs/ui)

UseToken
Page / panel surfacespaper.elevation2 page, elevation3 panels + readout, elevation4 by-position panel & seat pod, elevation5 borders
Family colorshands dealt primary.main · unopened pots betting.range · facing an action betting.fold
Selected statesprimary.states.focus (bg) + primary.main @28% (ring); hover action.hover
Textvalue text.primary, label text.secondary, tab value text.ghost, scale hints text.disabled
F / C / Rbetting.fold / betting.call / betting.raise.default
Radiusxxs family edge · xsm rows/cells · sm panels/boxes · med readout · full chip + bars
Spacingspacing.gutter between blocks, spacing.panel inside panels
Typeh5 page title · subtitle2 panel titles · body3 values/labels · body4/cap keys and captions

3b. Things the DS cannot express (build custom, deliberately)

3c. Position colours — approved

4. Geometry

5. States

StateTreatment
Selected statHighlighted in both readout row and tab (single source of truth: selectedStat)
Hoveraction.hover on readout rows and tabs; series card border paper.elevation8
No spotHatched track + - in the value column + tooltip gtoHud.noSpot. Never 0.
Matrix impossible cellEmpty cell (reports convention for the diagonal) — no hatch at grid scale
Chart hoverDS ComposedChart per-card cursor + tooltip. No cross-card sync.

6. Responsive (desktop-first preset: xl≤1561, lg≤1280, md≤1024, sm≤640)

7. Data & i18n

8. Tests to add / update

9. Gates before PR