@putdotio/design 3.5.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DESIGN.md CHANGED
@@ -22,17 +22,17 @@ colors:
22
22
  danger: "hsl(358, 75%, 59%)"
23
23
  typography:
24
24
  ui:
25
- family: "\"GT America\", sans-serif"
25
+ family: '"GT America", sans-serif'
26
26
  weight: 400
27
27
  size: "1rem"
28
28
  lineHeight: 1.45
29
29
  display:
30
- family: "\"GT America\", sans-serif"
30
+ family: '"GT America", sans-serif'
31
31
  weight: 900
32
32
  size: "96px"
33
33
  lineHeight: 1.1
34
34
  mono:
35
- family: "\"Berkeley Mono\", monospace"
35
+ family: '"Berkeley Mono", monospace'
36
36
  weight: 400
37
37
  size: "0.875rem"
38
38
  lineHeight: 1.45
@@ -62,131 +62,90 @@ components:
62
62
 
63
63
  ## Overview
64
64
 
65
- put.io is a content-agnostic cloud file product. The interface should feel premium because the file browser is useful, fast, quiet, and honest about the data it has. Design for raw filenames, folders, transfers, storage, playback controls, and settings without pretending that structured media metadata is always available.
66
-
67
- The canonical source for tokens is DTCG-compatible JSON in `tokens/`. Generated artifacts in `dist/` and `system/tokens.css` are outputs. This file explains how to use those tokens.
65
+ put.io is a content-agnostic cloud file product. Its interface is a file browser: fast, quiet, and honest about the data it has. Design for raw filenames, folders, transfers, storage, playback, and settings; media metadata is usually absent.
68
66
 
69
67
  ## Binding Tiers
70
68
 
71
- A web-rendered mock is never a native spec. Every surface binds to the system at one of five tiers, and every specimen card in `system/preview/` declares its tier in a strip above the artboard (`system/preview/_tier.css`; if the wording here changes, change that file in the same PR).
69
+ A web mock is never a native spec. Every surface binds at one tier, and every card in `system/preview/` names its tier in a strip worded by `system/preview/_tier.css`.
72
70
 
73
- - **Tier 0: Foundations.** The token graph. Binds every tier, values only, never a component recipe.
74
- - **Tier 1: www · app · auth.** The full design system: the component library and `system/components.css` recipes. The recipes are the contract here, and only here.
75
- - **Tier 2: iOS · iPadOS · tvOS · watchOS · Android · Android TV.** Tokens only. Every component, composition, and page comes from the platform's human interface guidelines; the cards document how a stock control receives put.io tokens, never how to rebuild one.
76
- - **Tier 3: Roku.** Tier-2 token inheritance, plus room for put.io conventions in custom SceneGraph components.
77
- - **Tier 4: tv.put.io · Tizen · webOS.** The design system as on web, restrained to a generic, calm, list-first 10-foot interface.
71
+ - **Tier 0: Foundations.** The token graph. Values only; binds every tier.
72
+ - **Tier 1: www · app · auth.** The full system: tokens plus the `system/components.css` recipes, which are the contract here only.
73
+ - **Tier 2: iOS · iPadOS · tvOS · watchOS · Android · Android TV.** Tokens only. Components and pages come from the platform's guidelines; cards show how a stock control takes put.io tokens.
74
+ - **Tier 3: Roku.** Tier 2, plus put.io conventions in custom SceneGraph components.
75
+ - **Tier 4: tv.put.io · Tizen · webOS.** The web system, restrained to a calm, list-first 10-foot interface.
78
76
 
79
- A tier-1/3/4 card is a Component: put.io owns the recipe. A tier-2 card is an Element: the platform owns the control. Per-platform binding contracts live in `platforms/<platform>/DESIGN.md`. One rule holds on every tier: every file-type icon is brand yellow, Phosphor, never a text glyph.
77
+ Tier 1, 3, and 4 cards are Components (put.io owns the recipe); tier 2 cards are Elements (the platform owns the control). Per-platform contracts: `platforms/<platform>/DESIGN.md`. On every tier, file-type icons are brand-yellow Phosphor icons.
80
78
 
81
79
  ## Colors
82
80
 
83
- Yellow `#FDCE45` is the resting `--yellow-solid` brand value in both light and dark modes. Use it for the put.io brand mark, primary action fill, folder/file emphasis, progress, and focus affordances. Use `--yellow-solid-hover` for primary button hover feedback; do not invent alternate yellows, gradient yellow into a different accent, or use yellow as foreground text on light surfaces.
84
-
85
- Dark app surfaces are the default product mode, but the token contract includes both dark and light palettes. Light surfaces are allowed for landing, docs, and narrow product states where readability is stronger. Semantic red, green, and neutral scales should come from tokens rather than one-off CSS values.
81
+ - Yellow `#FDCE45` (`--yellow-solid`) is the brand value in both modes: brand mark, primary actions, folder and file emphasis, progress, focus. Hover is `--yellow-solid-hover`.
82
+ - Every yellow is one of these tokens, flat. Yellow text on light surfaces is `--yellow-text-secondary`.
83
+ - Dark is the default product mode; light serves landing pages, docs, and states that read better light.
84
+ - Red, green, and neutral come from their token scales.
86
85
 
87
86
  ## Typography
88
87
 
89
- Use GT America for product UI and display. Berkeley Mono is the single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, identifiers, hashes, magnet URIs, and filenames. Its tabular figures keep metadata columns aligned, so pair it with `font-variant-numeric: tabular-nums` wherever numbers sit in a column.
90
-
91
- The mono role is web and mobile only. TV surfaces have no mono face; 10-foot numerics render in GT America and should ask for its tabular figures wherever a number ticks or sits in a column. GT America Standard carries `tnum`, so any platform that can select OpenType features can honour this: web through `font-variant-numeric: tabular-nums`, React Native through the same font feature settings. Platforms that cannot select features get the default proportional figures, where `1` is 399/1000 em against `0` at 636, so a running timecode visibly reflows. Roku is the current example (its SceneGraph `Font` node accepts only a `uri` and a `size`), so budget 10-foot numeric layouts for proportional figures rather than assuming the columns line up.
92
-
93
- GT America is a Latin face: 523 codepoints covering Western and Central European Latin plus Turkish, with no Cyrillic, Greek, CJK, Arabic, Hebrew, Thai, or Hangul, and no dingbats: no `U+2713 ✓`, `U+2714`, `U+25B6 ▶`, or `U+2605 ★`. Two rules follow. Symbols are always icons, never text glyphs, because a checkmark typed as text renders as nothing. And because put.io shows raw filenames, user content routinely arrives outside that coverage: keep a fallback family behind GT America for any text that can hold a filename. Web gets this for free, since `"GT America", sans-serif` falls back per glyph, so the gap is invisible there and shows up on platforms that resolve one font per label with no per-glyph fallback, where uncovered characters become `.notdef` boxes. Treat a platform's fallback story for content text as part of adopting the face, not an afterthought.
94
-
95
- Body UI text should stay readable and dense. Use medium weight for labels and controls, bold weight only where the hierarchy earns it, and black weight for deliberate display moments.
96
-
97
- The root font-size is responsive like the product: 14px, stepping to 15px at the `--bp-lg` breakpoint. Body, labels, and inputs sit at `--fs-base` (1rem, equal to the root); captions and button labels sit at `--fs-sm` (0.875rem). The display tiers (`--fs-2xl`, `--fs-3xl`, `--fs-display`) are fixed pixel sizes and do not scale with the root.
88
+ - GT America sets product UI and display. Labels and controls are medium, hierarchy is bold, display is black.
89
+ - Berkeley Mono is the one mono face: numerics, timestamps, sizes, ETAs, counts, code, logs, identifiers, hashes, magnet URIs, filenames. Columns of numbers add `font-variant-numeric: tabular-nums`.
90
+ - TV product UI has no mono face (a debug overlay may use the platform's system monospace): 10-foot numerics use GT America `tnum`. Platforms without OpenType feature selection (Roku: `Font` takes only `uri` and `size`) get proportional figures, so budget timecodes for reflow (`1` is 399/1000 em, `0` is 636).
91
+ - GT America is Latin only (523 codepoints, Western and Central European plus Turkish, no dingbats such as `✓` `▶` `★`). Symbols are icons. Text that can hold a filename keeps a fallback family; web falls back per glyph, while one-font-per-label platforms must ship a fallback or render `.notdef` boxes.
92
+ - Root font-size is 14px, 15px from `--bp-lg`. Body, labels, inputs: `--fs-base`. Captions, button labels: `--fs-sm`. `--fs-2xl`, `--fs-3xl`, `--fs-display` are fixed pixels.
98
93
 
99
94
  ## Layout & Spacing
100
95
 
101
- put.io layouts are file-browser layouts first: lists, rows, columns, sidebars, toolbars, and sheets. Prefer whitespace and subtle dividers before card-heavy composition.
102
-
103
- The spacing scale is intentionally steep: `4, 8, 16, 32, 64, 128, 256, 512`. Use it consistently rather than tuning one-off gaps.
104
-
105
- Stacking uses the token scale, gapped for platform chrome: `--z-dropdown` (1000), `--z-sticky` (1100), `--z-modal` (1300), `--z-notification` (1400), `--z-tooltip` (1500). Never invent z-index numbers. Breakpoints (`--bp-sm`/`--bp-md`/`--bp-lg` = 640/768/1280px) mirror the product's responsive tiers; CSS media queries cannot read custom properties, so write the literal pixels in `@media` and keep them in sync with the tokens.
96
+ - Layouts are file-browser layouts: lists, rows, columns, sidebars, toolbars, sheets. Whitespace and dividers come before cards.
97
+ - Spacing steps: `4, 8, 16, 32, 64, 128, 256, 512`. Every gap is one of them.
98
+ - z-index: `--z-dropdown` 1000, `--z-sticky` 1100, `--z-modal` 1300, `--z-notification` 1400, `--z-tooltip` 1500.
99
+ - Breakpoints `--bp-sm`/`--bp-md`/`--bp-lg` are 640/768/1280px; `@media` takes the literal pixels.
106
100
 
107
101
  ## Elevation & Depth
108
102
 
109
- Elevation should clarify interaction, not decorate. Use borders and soft shadows for popovers, modals, menus, TV focus, and draggable/drop targets. Avoid decorative glow except where it is part of a focused TV state.
103
+ Borders and soft shadows mark interaction: popovers, modals, menus, TV focus, drag and drop targets. Glow belongs only to focused TV states.
110
104
 
111
105
  ## Shapes
112
106
 
113
- The default radius is `6px`. Use `4px` for tight controls, `8px` or `10px` for larger panels, and pill radius for segmented controls and badges.
114
-
115
- TV is the exception: every 10-foot surface (list rows, buttons, sheets, cards, code tiles, keys) uses the single `--radius-tv` (`12px`). TV has one radius, not a scale; the only other radii on a TV surface are intentional pills and circles.
107
+ Default radius `6px`; `4px` for tight controls, `8px` or `10px` for panels, pill for segmented controls and badges. TV uses one radius, `--radius-tv` (`12px`), plus intentional pills and circles.
116
108
 
117
109
  ## Components
118
110
 
119
- The component recipes below and in `system/components.css` are the
120
- **web binding** of the token graph, and they bind the web surfaces in full.
121
- Other tiers inherit as [Binding Tiers](#binding-tiers) describes; every TV
122
- surface, native ones within their platform conventions, treats the web TV
123
- list-first look as the shared family look.
124
-
125
- Buttons have three tiers on one axis: emphasis. **Primary** is the brand fill: `--button-primary-bg` at rest, `--button-primary-bg-hover` on hover so the CTA never feels inert, label `--button-primary-fg`. Yellow is reserved for primary commands, and an action group carries at most one. **Secondary** is a quiet fill: `--button-default-bg` (aliased as `--button-secondary-bg`) plus a border at `--border`, with hover stepping both (`--button-default-bg-hover`, `--border-hover`). Because it fills, it reads to a hard edge exactly like the primary, so the two compare safely side by side. **Ghost** is text only: no fill, no border. `--button-ghost-fg` at rest; hover paints `--button-ghost-bg-hover` behind the label and lifts it to `--button-ghost-fg-hover`. The label lift is mandatory: the faint fill is never the only hover cue. Disabled drops the colour to `--solid` at full opacity rather than fading the box. Use ghost for anything quieter than a filled secondary: a header `Sign in`, toolbar text actions, `Cancel` beside a filled confirm. An icon-only ghost is the quiet icon button, on the same tokens.
126
-
127
- Outline is not a tier. The system ships no transparent-background bordered button, and consumers must not hand-roll one. The pairing rule behind that: never place a border-only button in the same action group as a solid fill. At identical box heights the outlined shape reads smaller, because a solid resolves to a hard edge while a low-contrast hairline resolves to a soft one, and the eye compares the two extents differently. It is worst on dark surfaces, where a 15%-white hairline is barely visible at all. If a bordered treatment must sit near a fill, it must itself fill (the secondary tier) and carry its border at `--border`-step contrast, never a sub-20% hairline on transparent.
128
-
129
- Button geometry is tokenized so every tier and variant of a given size shares an identical box; tiers change colour only, which makes cross-variant comparison safe by construction. There are four sizes and no others. The default is `36px` tall with `12px` of horizontal padding, an `--fs-sm` uppercase label and `1px` of tracking; `md` is `32px` at `0.8px` tracking, `sm` is `28px` at `0.6px`, and `xs` is `24px` with `8px` padding, an `--fs-xs` label and `0.3px` tracking. Read them from `--button-height` (`-md`, `-sm`, `-xs`), `--button-padding-x` (`-xs`), `--button-label-size` (`-xs`), `--button-tracking` (`-md`, `-sm`, `-xs`), `--button-icon-size` and `--button-gap` rather than restating the numbers. Every button token aliases the neutral scale, which carries both modes, so a dark bar on a light page is a `.dark`-scoped region and never hand-picked dark literals. The sticky site top bar is `--nav-height` (`56px`), or `--nav-height-compact` (`52px`) on narrow viewports, and its CTA is the `md` box via `--nav-cta-height`.
130
-
131
- TV needs no carve-out here: TV buttons keep the `tv` scale box and `--radius-tv`, and TV focus restores a visible box (`--component-bg-active` plus a border step), so the ghost tier stays legible in the 10-foot focus model.
132
-
133
- File rows are the core component: preserve raw names, keep metadata compact, and make hover/focus states obvious without inflating row height.
134
-
135
- Form fields use the shared `--field-*` aliases: `--field-bg` for the fill, `--field-border` for the resting border, and `--field-ring` for focus. Invalid fields opt in with `aria-invalid="true"` and use red border/text only; do not add red fill. Raised panels use the shared `--panel-*` aliases instead of auth- or screen-specific panel variables.
136
-
137
- Browser Auth composition is fluid up to a `340px` column. It uses the standard
138
- `36px` field recipe, a `30.33px` wordmark box (the `26px` ink height multiplied
139
- by its `7/6` view-box ratio), and centered headings at `19px` / weight
140
- `500`. The prompt and its action form a centered two-line footer. User-facing
141
- actions say “Sign in” and “Sign up”, and an invalid credential response uses
142
- “That username or password doesn't look right” with no terminal punctuation.
143
- The shipped
144
- `web-s06-auth` screen plus `web-p00c-auth-signin`, `web-p00d-auth-signup`, and
145
- `web-p00e-auth-2fa` are the Tier-1 composition references for this contract.
146
-
147
- Form feedback uses the exported `.form-callout` recipe. Its block form reads
148
- the `--alert-*` families and selects `info`, `success`, or `error` with
149
- `data-state`; `.inline` removes the surface and border while keeping the same
150
- semantic text colour. Callouts remain messages, not containers for unrelated
151
- form layout. Their parent uses `.form-group`, whose `6px` gap owns the spacing
152
- between a control and its inline feedback.
111
+ `system/components.css` is the web binding and binds web surfaces in full. Every TV surface shares the web TV list-first look; native ones within their platform conventions.
153
112
 
154
- One-time passwords use one semantic `.otp-input`, never six independent
155
- inputs. Place it with two three-slot `.otp-group` elements, one
156
- `.otp-separator`, and six visual `.otp-slot` elements. The input carries
157
- `inputmode="numeric"`, `autocomplete="one-time-code"`, `maxlength="6"`, and
158
- `aria-invalid`; script synchronizes slot text plus `filled` / `active` state.
159
- Pointer interaction maps the clicked visual slot to the backing input's
160
- selection; do not rely on the transparent input's native glyph hit testing.
161
- Editing an invalid code clears its stale error and disables retry until all six
162
- digits are present. Retrying enters the same locked verifying state as the
163
- default form.
164
- The root uses `data-state="verifying"` while submission is in flight and keeps
165
- the entered digits visible; the backing input is disabled for the same period,
166
- and the action label announces the transition through a polite live region.
167
- Successful verification uses `data-state="success"`, retains the six digits in
168
- a read-only input, announces automatic continuation with a `role="status"`
169
- callout, and does not leave another action on screen. The consumer owns the
170
- backend transition and navigation timing.
171
- The reference omits recovery-code entry until its consumer owns a working
172
- route. Do not expose a specimen-only recovery action.
173
- Groups, not slots, own the responsive width, so the complete control contracts
174
- to narrow Auth cards without horizontal scrolling.
113
+ ### Buttons
175
114
 
176
- TV components are list-first and focus-first. A TV app is still a file browser, not a poster wall. Roku, native Apple, Android, and web TV repos should consume the generic tokens and generate their own platform bindings.
115
+ - **Primary:** `--button-primary-bg`, hover `--button-primary-bg-hover`, label `--button-primary-fg`. At most one per action group.
116
+ - **Secondary:** `--button-default-bg` plus a `--border` border; hover steps both (`--button-default-bg-hover`, `--border-hover`).
117
+ - **Ghost:** text only, `--button-ghost-fg`; hover adds `--button-ghost-bg-hover` and lifts the label to `--button-ghost-fg-hover`. Use it for anything quieter than secondary: header `Sign in`, toolbar actions, `Cancel` beside a confirm, icon-only buttons.
118
+ - **Disabled:** colour drops to `--solid` at full opacity.
119
+ - A bordered button always fills (secondary). Border-only buttons read smaller beside solid fills and vanish on dark.
120
+ - Four sizes share one box per size across tiers: default 36px, `md` 32px, `sm` 28px, `xs` 24px. Read `--button-height*`, `--button-padding-x*`, `--button-label-size*`, `--button-tracking*`, `--button-icon-size`, `--button-gap`.
121
+ - A dark bar on a light page is a `.dark`-scoped region.
122
+ - The sticky top bar is `--nav-height` (`--nav-height-compact` on narrow viewports); its CTA is `md` via `--nav-cta-height`.
123
+ - TV buttons use the `tv` scale and `--radius-tv`; focus restores a visible box.
177
124
 
178
- TV surfaces are solid token colors: no translucent materials and no blur. Focus is expressed as a fill, never a lift: rows go transparent to `--component-bg-active`, and web TV buttons step both their fill and their border at one fixed edge width. The `tv.focus` group carries every value: `tv.focus.row.*` for the row fill that web TV and Roku share, `tv.focus.control.*` for the web TV fill and border step, and `tv.focus.borderWidth` (`3px`) for the edge. Roku dialog buttons are borderless and follow their primary, secondary, or danger variant instead. Navigation is a Home hub that pushes full screens onto a stack; there is no tab bar. Menus are centred modals, not popovers anchored to a trigger. These focus and material rules bind the web TV and Roku surfaces; a native TV binding follows its platform's own control conventions where the platform owns focus. Apple tvOS buttons use the system focus treatment (adopted 2026-08) while non-control TV surfaces stay solid everywhere.
125
+ ### Rows, fields, and panels
179
126
 
180
- The 10-foot scale (type steps, spacing ramp, overscan ratios, radius, focus, and z-indices) lives in the `tv` token group, carried as data for platform adapters rather than emitted into web CSS. Overscan ratios declare their viewport axis: multiply `tv.overscan.x` by viewport width and `tv.overscan.y` by viewport height, then compose screen padding locally with the spacing tokens. Two rules do not cross over: TV has no mono face, and TV stacks `tv.z.overlay` above `tv.z.toast`, the reverse of the web `--z-*` order. Emit the two scales separately. Cite the `tv` group by its graph path, never as `--tv-*`: those names are read from the DTCG artifacts by platform adapters and are deliberately absent from `dist/css/tokens.css`, so CSS-variable syntax would promise a `var()` that resolves to nothing.
127
+ - File rows are the core component: raw names, compact metadata, obvious hover and focus at the same row height.
128
+ - Fields read `--field-bg`, `--field-border`, and `--field-ring` (focus). `aria-invalid="true"` turns border and text red; the fill stays.
129
+ - Raised panels read `--panel-*`.
130
+ - Feedback uses `.form-callout`: `--alert-*` colours, `data-state` `info`, `success`, or `error`; `.inline` drops surface and border. It holds a message only. `.form-group` (`6px` gap) spaces a control and its feedback.
181
131
 
182
- ## Do's and Don'ts
132
+ ### Auth
183
133
 
184
- Do show raw filenames such as `The.Wire.S03E04.Back.Burners.1080p.BluRay.x264-DEMAND.mkv`.
134
+ - A fluid column up to `340px`, the `36px` field recipe, a `30.33px` wordmark box (26px ink × 7/6), centred `19px`/500 headings, and a centred two-line prompt footer.
135
+ - Copy: “Sign in”, “Sign up”, and “That username or password doesn't look right” with no terminal punctuation.
136
+ - References: `web-s06-auth`, `web-p00c-auth-signin`, `web-p00d-auth-signup`, `web-p00e-auth-2fa`.
137
+ - One-time codes are one `.otp-input` (`inputmode="numeric"`, `autocomplete="one-time-code"`, `maxlength="6"`, `aria-invalid`) shown through two `.otp-group`s, one `.otp-separator`, and six `.otp-slot`s; script syncs slot text and `filled`/`active`, and maps a clicked slot to the input selection.
138
+ - Editing an invalid code clears the error; retry waits for six digits. `data-state="verifying"` disables the input, keeps the digits, and announces through a polite live region. `data-state="success"` keeps the digits read-only, announces continuation in a `role="status"` callout, and shows no further action; the consumer owns navigation.
139
+ - No recovery-code action until a consumer owns the route. Groups own the responsive width, so the control fits narrow cards without scrolling.
185
140
 
186
- Do use Phosphor-style inline SVG for forward-looking icon guidance and no emoji in product UI.
141
+ ### TV
187
142
 
188
- Do keep `DESIGN.md`, `tokens/`, `dist/`, and `system/tokens.css` in agreement.
143
+ - TV is list-first and focus-first: a file browser, not a poster wall. TV repos consume the generic tokens and own their bindings.
144
+ - Surfaces are solid token colours, no blur or translucency. Focus is a fill: rows go to `--component-bg-active`; web TV buttons step fill and border at `tv.focus.borderWidth` (`3px`). Values live in `tv.focus.row.*` and `tv.focus.control.*`. Roku dialog buttons are borderless and follow their variant.
145
+ - Navigation is a Home hub pushing full screens; no tab bar. Menus are centred modals.
146
+ - These rules bind web TV and Roku. Native TV follows its platform where the platform owns focus; tvOS buttons use system focus, other surfaces stay solid.
147
+ - The `tv` token group (type, spacing, overscan, radius, focus, z) is adapter data, absent from web CSS: cite it by graph path, never `--tv-*`. Overscan multiplies `tv.overscan.x` by width and `tv.overscan.y` by height. `tv.z.overlay` sits above `tv.z.toast`, the reverse of web.
189
148
 
190
- Don't publish private material in this public repo; the enumerated list lives in the repo's `docs/DISTRIBUTION.md` (Public Safety).
149
+ ## Content and Icons
191
150
 
192
- Don't generate platform-native outputs (Swift, Kotlin, Android XML, Roku) from this repo; platform repos own their adapters. Policy in the repo's `docs/DISTRIBUTION.md`.
151
+ Show filenames exactly as stored, such as `The.Wire.S03E04.Back.Burners.1080p.BluRay.x264-DEMAND.mkv`. Icons are Phosphor-style inline SVG; product UI carries no emoji.
package/README.md CHANGED
@@ -18,20 +18,12 @@
18
18
 
19
19
  Live guide: [design.put.io](https://design.put.io)
20
20
 
21
- This repo owns the public contract behind it: DTCG token sources, generated
22
- CSS/JSON/TypeScript artifacts, brand assets, preview cards, and deployable
23
- static guidance.
24
-
25
21
  ## Install
26
22
 
27
23
  ```bash
28
24
  npm install @putdotio/design
29
25
  ```
30
26
 
31
- Token sources live in [`tokens`](tokens), generated artifacts in
32
- [`dist`](dist), and package-safe brand assets in
33
- [`system/assets`](system/assets).
34
-
35
27
  ## Use
36
28
 
37
29
  Import the generated tokens first. Tier-1 web surfaces also import the
@@ -47,22 +39,22 @@ Component recipes consume semantic aliases such as `--field-*`, `--panel-*`,
47
39
 
48
40
  Package entrypoints, from `exports` in [package.json](package.json):
49
41
 
50
- | Subpath | File |
51
- | --- | --- |
52
- | `@putdotio/design/css` | [`dist/css/tokens.css`](dist/css/tokens.css) |
53
- | `@putdotio/design/components` | [`system/components.css`](system/components.css) |
54
- | `@putdotio/design/tokens` | [`dist/tokens.flat.json`](dist/tokens.flat.json) |
55
- | `@putdotio/design/tokens/meta` | [`dist/tokens.js`](dist/tokens.js), typed by [`dist/tokens.d.ts`](dist/tokens.d.ts) |
56
- | `@putdotio/design/tokens/dtcg` | [`dist/tokens.dtcg.json`](dist/tokens.dtcg.json) |
57
- | `@putdotio/design/tokens/figma` | [`dist/figma/putio.tokens.json`](dist/figma/putio.tokens.json) |
58
- | `@putdotio/design/assets/<file>` | [`system/assets`](system/assets) |
59
- | `@putdotio/design/design.md` | [`DESIGN.md`](DESIGN.md) |
42
+ | Subpath | File |
43
+ | -------------------------------- | ----------------------------------------------------------------------------------- |
44
+ | `@putdotio/design/css` | [`dist/css/tokens.css`](dist/css/tokens.css) |
45
+ | `@putdotio/design/components` | [`system/components.css`](system/components.css) |
46
+ | `@putdotio/design/tokens` | [`dist/tokens.flat.json`](dist/tokens.flat.json) |
47
+ | `@putdotio/design/tokens/meta` | [`dist/tokens.js`](dist/tokens.js), typed by [`dist/tokens.d.ts`](dist/tokens.d.ts) |
48
+ | `@putdotio/design/tokens/dtcg` | [`dist/tokens.dtcg.json`](dist/tokens.dtcg.json) |
49
+ | `@putdotio/design/tokens/figma` | [`dist/figma/putio.tokens.json`](dist/figma/putio.tokens.json) |
50
+ | `@putdotio/design/assets/<file>` | [`system/assets`](system/assets) |
51
+ | `@putdotio/design/design.md` | [`DESIGN.md`](DESIGN.md) |
60
52
 
61
53
  ## Docs
62
54
 
63
55
  - [Design contract](DESIGN.md)
64
56
  - [Design guide structure](system/README.md)
65
- - [Distribution](docs/DISTRIBUTION.md)
57
+ - [Distribution](docs/distribution.md)
66
58
  - [Contributing](CONTRIBUTING.md)
67
59
  - [Security](https://github.com/putdotio/.github/blob/main/SECURITY.md)
68
60
  - [Agent guide](AGENTS.md)
@@ -878,7 +878,7 @@
878
878
  "mono": {
879
879
  "$type": "fontFamily",
880
880
  "$value": "\"Berkeley Mono\", monospace",
881
- "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only — TV has no mono face."
881
+ "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only. TV product UI has no mono face; a TV debug overlay may use the platform's system monospace."
882
882
  }
883
883
  },
884
884
  "fontSize": {
@@ -4749,7 +4749,7 @@
4749
4749
  "mono": {
4750
4750
  "$type": "fontFamily",
4751
4751
  "$value": "\"Berkeley Mono\", monospace",
4752
- "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only — TV has no mono face.",
4752
+ "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only. TV product UI has no mono face; a TV debug overlay may use the platform's system monospace.",
4753
4753
  "$extensions": {
4754
4754
  "putio": {
4755
4755
  "cssName": "font-mono",
@@ -3225,7 +3225,7 @@
3225
3225
  "mode": "global",
3226
3226
  "value": "\"Berkeley Mono\", monospace",
3227
3227
  "originalValue": "\"Berkeley Mono\", monospace",
3228
- "description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only — TV has no mono face."
3228
+ "description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only. TV product UI has no mono face; a TV debug overlay may use the platform's system monospace."
3229
3229
  },
3230
3230
  "typography.fontSize.xs": {
3231
3231
  "cssName": "--fs-xs",
package/dist/tokens.js CHANGED
@@ -3227,7 +3227,7 @@ export const tokens = {
3227
3227
  "mode": "global",
3228
3228
  "value": "\"Berkeley Mono\", monospace",
3229
3229
  "originalValue": "\"Berkeley Mono\", monospace",
3230
- "description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only — TV has no mono face."
3230
+ "description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only. TV product UI has no mono face; a TV debug overlay may use the platform's system monospace."
3231
3231
  },
3232
3232
  "typography.fontSize.xs": {
3233
3233
  "cssName": "--fs-xs",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/design",
3
- "version": "3.5.0",
3
+ "version": "3.6.0",
4
4
  "description": "Public put.io design tokens, brand assets, and design-system guidance.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,24 +68,28 @@
68
68
  "dev": "node scripts/serve-system.ts",
69
69
  "tokens:build": "node scripts/build-tokens.ts",
70
70
  "tokens:check": "node scripts/build-tokens.ts && node scripts/check-component-css.ts && node scripts/verify-design-system.ts",
71
+ "artifact": "node scripts/build-artifact.ts && node scripts/build-canvases.ts",
71
72
  "test:config": "node --test tests/playwright-config.test.mjs",
72
73
  "typecheck:scripts": "tsc --noEmit --ignoreConfig --skipLibCheck --module ESNext --moduleResolution Bundler --target ES2022 --strict --types node scripts/*.ts playwright.config.ts tests/e2e/*.ts sst.config.ts infra/*.ts",
73
74
  "html:check": "html-validate \"system/**/*.html\"",
75
+ "markdown:check": "oxfmt --check \"**/*.md\"",
74
76
  "e2e": "playwright test",
75
- "verify": "pnpm typecheck:scripts && pnpm tokens:check && pnpm html:check",
77
+ "verify": "pnpm typecheck:scripts && pnpm tokens:check && pnpm artifact && pnpm html:check && pnpm markdown:check",
76
78
  "verify:full": "pnpm verify && pnpm test:config && pnpm e2e && npm pack --dry-run",
77
79
  "check": "pnpm verify",
78
80
  "deploy:production": "sst deploy --stage production"
79
81
  },
80
82
  "devDependencies": {
81
83
  "@axe-core/playwright": "^4.11.3",
84
+ "@phosphor-icons/web": "2.1.1",
82
85
  "@playwright/test": "^1.60.0",
83
86
  "@types/node": "^26.0.0",
84
87
  "html-validate": "^11.2.0",
88
+ "oxfmt": "0.73.0",
85
89
  "sst": "^4.14.3",
86
90
  "style-dictionary": "^5.4.1",
87
91
  "typescript": "^7.0.2",
88
92
  "yaml": "^2.9.0"
89
93
  },
90
- "packageManager": "pnpm@12.4.2"
94
+ "packageManager": "pnpm@12.13.0"
91
95
  }
package/system/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # put.io design system
2
2
 
3
- This folder holds the generated site tokens, preview cards, and design-system index.
3
+ The guide site: generated site tokens, component CSS, preview cards, and the
4
+ design-system index.
4
5
 
5
6
  ## Layout
6
7
 
7
8
  - `index.html` redirects to `design-system.html`, the guide and preview-card
8
- index. `design-system-light.html` is a legacy redirect to
9
+ index. `design-system-light.html` redirects to
9
10
  `design-system.html?theme=light`.
10
- - `tokens.css` is generated from `../tokens/**/*.tokens.json`; do not edit it.
11
+ - `tokens.css` is generated from `../tokens/**/*.tokens.json`; edit the token
12
+ sources and rebuild.
11
13
  - `components.css` is the Tier-1 component layer (the web contract).
12
14
  - `tv.css` is the TV / 10-foot component CSS, scoped to `.tv` artboards.
13
15
  - `assets/` holds logos, favicons, retro marks, and app icons.
@@ -24,87 +26,84 @@ states, a spec strip, a don't block.
24
26
  ## Fonts
25
27
 
26
28
  Preview pages load the public put.io font CSS from `static.put.io`, the same
27
- source as the product. Do not commit font files here. Each HTML file links both
28
- families in `<head>`:
29
+ source as the product; font licensing is in
30
+ [Distribution](../docs/distribution.md#fonts-and-assets). Each preview links
31
+ both families in `<head>`:
29
32
 
30
33
  ```html
31
- <link rel="stylesheet" href="https://static.put.io/fonts/gt-america/standard/font.css">
32
- <link rel="stylesheet" href="https://static.put.io/fonts/berkeley-mono/variable/font.css">
34
+ <link rel="stylesheet" href="https://static.put.io/fonts/gt-america/standard/font.css" />
35
+ <link rel="stylesheet" href="https://static.put.io/fonts/berkeley-mono/variable/font.css" />
33
36
  ```
34
37
 
35
- GT America Mono is retired: all mono text reads `--font-mono` (Berkeley Mono),
36
- whose tabular figures carry the numerics. Do not re-add the
37
- `gt-america/mono/font.css` load.
38
+ Those two links are the full font set: mono text reads `--font-mono` (Berkeley
39
+ Mono), whose tabular figures carry the numerics.
38
40
 
39
- Canonical weight mapping (matches the brand font host):
40
- `100 ultra-light · 200 thin · 300 light · 400 regular · 500 medium · 700 bold · 900 black`
41
+ Weight names on the font host:
42
+ `100 ultra-light · 200 thin · 300 light · 400 regular · 500 medium · 700 bold · 900 black`.
43
+ The tokens use four of them through `--fw-regular`, `--fw-medium`, `--fw-bold`,
44
+ and `--fw-black`.
41
45
 
42
46
  ## Using tokens
43
47
 
44
- For the static guide, pages in `system/` load the generated site stylesheet:
48
+ Guide pages in `system/` load the generated site stylesheet:
45
49
 
46
50
  ```html
47
- <link rel="stylesheet" href="./tokens.css">
51
+ <link rel="stylesheet" href="./tokens.css" />
48
52
  ```
49
53
 
50
54
  The canonical source is DTCG-compatible JSON in [`../tokens`](../tokens). This
51
55
  site stylesheet (`system/tokens.css`) and the package stylesheet
52
56
  (`dist/css/tokens.css`, exported as `@putdotio/design/css`) are generated from
53
- the same source. Guide and component CSS should consume custom properties rather
54
- than hard-coded palette values. Brand yellow is `var(--yellow-solid)`,
55
- canonical `#FDCE45`; the emitted hsl form is pinned in
56
- [`../DESIGN.md`](../DESIGN.md) front-matter, and the `--yellow-solid-hover` →
57
- `--button-primary-bg-hover` derivation is specified in the repo's
58
- `docs/DISTRIBUTION.md`.
59
-
60
- Stacking and breakpoints are tokenized (`--z-*`, `--bp-*`) and the root
61
- font-size is responsive; the canonical values live in
62
- [`../DESIGN.md`](../DESIGN.md) (Layout & Spacing, Typography). Guide-specific
63
- rule: CSS media queries can't read custom properties, so write the literal px
64
- in `@media` and keep it in sync with the `--bp-*` token.
57
+ the same source. Brand yellow is `var(--yellow-solid)`; its emitted hsl form is
58
+ pinned in the [`../DESIGN.md`](../DESIGN.md) frontmatter, and the
59
+ `--button-primary-bg-hover` derivation is in
60
+ [Distribution](../docs/distribution.md#generated-files).
61
+
62
+ Stacking, breakpoints, the root font-size, and the `@media` literal rule live
63
+ in [Layout & Spacing](../DESIGN.md#layout--spacing) and
64
+ [Typography](../DESIGN.md#typography).
65
65
 
66
66
  Package consumers import as in the [README](../README.md#use).
67
67
 
68
68
  For **TV preview cards**, also load `tv.css`, which adds the `.tv`, `.scr`,
69
69
  `.row`, solid surface tiers, and player chrome on top of the tokens, authored
70
70
  1:1 at 1920×1080 (a px in the file is a px on the TV). Every component selector
71
- is scoped under `.tv`, so the file is safe to bundle globally without bleeding
72
- into web components. Platform repos still implement TV components in their
73
- native UI stacks:
71
+ is scoped under `.tv`, so the file can be bundled globally alongside web
72
+ components. Platform repos implement TV components in their native UI stacks:
74
73
 
75
74
  ```html
76
- <link rel="stylesheet" href="./tokens.css">
77
- <link rel="stylesheet" href="./tv.css">
75
+ <link rel="stylesheet" href="./tokens.css" /> <link rel="stylesheet" href="./tv.css" />
78
76
  ```
79
77
 
80
78
  TV material, focus, and the `tv` token group rules live in
81
- [`../DESIGN.md`](../DESIGN.md) (Components). Guide specifics: the `tv` group is
82
- `mode: "tv"` and read from `dist/tokens.flat.json`; only `--radius-tv` is
83
- global and reachable in CSS.
79
+ [Components](../DESIGN.md#components). Guide specifics: the `tv` group is
80
+ `mode: "tv"` and read from `dist/tokens.flat.json`; `--radius-tv` is the one
81
+ `tv` value the token CSS emits, so `tv.css` declares the `--tv-*` properties
82
+ its cards use itself.
84
83
 
85
84
  ## Core rules
86
85
 
87
- - **Yellow `#FDCE45` is the brand constant at rest.** Full rule and approved uses in [`../DESIGN.md`](../DESIGN.md) (Colors). Guide specifics: `--yellow-solid-hover` (`#F3C435`) is the hover peer with its own canonical hex in both modes, and focus rings use the yellow at 35% alpha (`--shadow-focus-color`).
88
- - **Yellow as text on light needs `--yellow-text-secondary`** (≈5:1, AA); the brand `--yellow-solid` is fill-only on light backgrounds.
89
- - **Labels on yellow** use `--primary-foreground` (`hsl(38, 65%, 10%)`, a warm dark, never pure black). Passes AA on the yellow fill without feeling "hard."
90
- - **Icons: Phosphor-style inline SVG.** No emoji, ever.
91
- - **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on `<html>`; do not fork the page or rebuild a light-specific copy. Components must consume semantic tokens (`--bg`, `--text`, `--border`, `--accent`, …), not raw `#hex` or `hsl()`.
92
- - **Fields and panels use the shared aliases** (`--field-*`, `--panel-*`); full rules, including the `aria-invalid` red-border-only requirement, are in [`../DESIGN.md`](../DESIGN.md) (Components).
93
- - **Wordmarks are optically pre-centred.** All three logo SVGs carry built-in top padding in the viewBox (retro pair `0 -16 376 112`, `logo.svg` `0 -24.44 621.79 176.055`). Flex-centre as-is; never add a `translateY` nudge. Height-set placements size boxes at 7/6 of the ink height.
94
- - **Content-agnostic.** put.io doesn't know what the user's files are. Never parse `The.Wire.S03E04.1080p.mkv` into `The Wire`. Raw filenames only. See the root [`DESIGN.md`](../DESIGN.md) for the full principle.
86
+ - **Yellow** follows [Colors](../DESIGN.md#colors). Guide specifics: `--yellow-solid-hover` is the hover peer with one value in both modes, and button focus-visible rings use `--shadow-focus` (the yellow at 35% alpha, `--shadow-focus-color`). Form fields focus with the neutral `--field-ring` instead.
87
+ - **Yellow as text on light** uses `--yellow-text-secondary` (≈5:1, AA); `--yellow-solid` is fill-only on light backgrounds.
88
+ - **Labels on yellow** use `--primary-foreground`, a warm dark that passes AA on the yellow fill.
89
+ - **One design, two modes.** Light and dark are the same markup and components with different token values. Toggle `.dark` on `<html>`; one page serves both modes. Components consume semantic tokens (`--bg`, `--text`, `--border`, `--accent`, …) instead of raw `#hex` or `hsl()`.
90
+ - **Wordmarks are optically pre-centred.** All three logo SVGs carry built-in top padding in the viewBox (retro pair `0 -16 376 112`, `logo.svg` `0 -24.44 621.79 176.055`). Flex-centre them as-is, with no `translateY` nudge. Height-set placements size boxes at 7/6 of the ink height.
91
+
92
+ Icons, fields and panels, and raw filenames follow the root
93
+ [Components](../DESIGN.md#components) and
94
+ [Content and Icons](../DESIGN.md#content-and-icons).
95
95
 
96
96
  ## Binding tiers
97
97
 
98
98
  Tier definitions, the Component/Element distinction, and the `preview/_tier.css`
99
- pairing rule live in [`../DESIGN.md`](../DESIGN.md) (Binding Tiers). Never read
100
- an Element card as a build-a-custom-control instruction.
99
+ pairing rule live in [Binding Tiers](../DESIGN.md#binding-tiers). An Element
100
+ card shows how a stock control receives tokens.
101
101
 
102
102
  Native cards are drawn 1:1 in their own unit (iPhone 393×852pt, iPad
103
103
  1024×768pt, Apple Watch 176×215pt, Android 412×915dp, TV 1920×1080px), so a
104
104
  number written on a card is the number in the HIG or the Material spec. Those
105
- metrics are Apple's and Google's; they are **not** put.io spacing tokens and
106
- must never be promoted into the graph. Device bezels and on-video chrome stay
107
- deliberately un-tokenised.
105
+ metrics are Apple's and Google's and stay out of the put.io token graph. Device
106
+ bezels and on-video chrome stay untokenised.
108
107
 
109
108
  ## Theme System
110
109
 
@@ -114,47 +113,46 @@ The guide (`design-system.html`) owns the theme toggle. It writes
114
113
  so embeds flip live without reloading. Each preview's inline bootstrap reads
115
114
  the same storage key on standalone open, and `preview/_resize.js` listens for
116
115
  the parent broadcast when embedded. A URL query (`?theme=light` or
117
- `?theme=dark`) overrides both, which is useful for screenshots.
116
+ `?theme=dark`) overrides both, for screenshots.
118
117
 
119
- Fixed-mode previews opt out with `<html data-theme-lock="dark">`. This is only
120
- for product mockups whose visual language is intrinsically one mode. A 10-foot
118
+ Fixed-mode previews opt out with `<html data-theme-lock="dark">`, reserved for
119
+ product mockups whose visual language is intrinsically one mode. A 10-foot
121
120
  screen, a native app mockup, and on-video player chrome are fixed-mode
122
121
  surfaces, so the `ios-*`, `android-*`, `androidtv-*`, `tvos-*`, `watchos-*`,
123
- `roku-*`, and `tv-*` cards plus `web-p06-player.html` are locked. Locked
124
- previews ignore both `localStorage` and parent broadcasts.
125
- New component previews should not use the lock; the tokens render them
126
- correctly in both modes.
122
+ `roku-*`, `tv-*`, and `mobile-*` cards plus `web-p06-player.html` are locked.
123
+ Locked previews ignore `localStorage`, parent broadcasts, and the URL query.
124
+ New component previews stay unlocked, so the tokens render them in both modes.
127
125
 
128
126
  ## shadcn / Base UI interop
129
127
 
130
128
  The token layer aliases the canonical shadcn token names, so shadcn/ui blocks,
131
129
  Base UI components, and Tailwind presets resolve against put.io values without
132
- renaming. Always alias, never duplicate, so the two name systems cannot drift.
130
+ renaming. Each shadcn name aliases a put.io token and carries no value of its
131
+ own, so the two name systems cannot drift.
133
132
 
134
- | shadcn name | put.io target |
135
- | --- | --- |
136
- | `--primary` / `--primary-foreground` | `--yellow-solid` / `var(--primary-foreground)` (warm dark) |
133
+ | shadcn name | put.io target |
134
+ | -------------------------------------------- | ---------------------------------------------------------- |
135
+ | `--primary` / `--primary-foreground` | `--yellow-solid` / `var(--primary-foreground)` (warm dark) |
137
136
  | `--destructive` / `--destructive-foreground` | `--red-solid` / `var(--destructive-foreground)` (= `#fff`) |
138
- | `--success` / `--success-foreground` | `--green-solid` / `var(--success-foreground)` (= `#fff`) |
139
- | `--muted` / `--muted-foreground` | `--bg-secondary` / `--text-secondary` |
140
- | `--accent` / `--accent-foreground` | `--component-bg` / `--text` |
141
- | `--card` / `--card-foreground` | `--component-bg` / `--text` |
142
- | `--popover` / `--popover-foreground` | `--component-bg` / `--text` |
143
- | `--input` | `--border` |
144
- | `--ring` | `--shadow-focus-color` |
145
- | `--background` / `--foreground` | `--bg` / `--text` |
146
-
147
- There is also a full `--input-*` set (`-bg`, `-border`, `-border-hover`,
148
- `-border-focus`, `-ring`, `-text`, `-placeholder`, `-radius`) that aliases the
149
- `--field-*` tokens one-for-one, so shadcn-shaped input code themes without
150
- renaming. **Author against `--field-*`;** the `--input-*` names exist only as the
151
- alias bridge and must never carry their own value.
152
-
153
- The foreground tokens (`--primary-foreground`, `--destructive-foreground`,
154
- `--success-foreground`) encode the mode-safe inverse pair; always read these
155
- instead of writing literal `#000` / `#fff` on a solid surface.
156
-
157
- put.io components prefer the long descriptive names (`--component-bg-hover`,
137
+ | `--success` / `--success-foreground` | `--green-solid` / `var(--success-foreground)` (= `#fff`) |
138
+ | `--muted` / `--muted-foreground` | `--bg-secondary` / `--text-secondary` |
139
+ | `--accent` / `--accent-foreground` | `--component-bg` / `--text` |
140
+ | `--card` / `--card-foreground` | `--component-bg` / `--text` |
141
+ | `--popover` / `--popover-foreground` | `--component-bg` / `--text` |
142
+ | `--input` | `--border` |
143
+ | `--ring` | `--shadow-focus-color` |
144
+ | `--background` / `--foreground` | `--bg` / `--text` |
145
+
146
+ A full `--input-*` set (`-bg`, `-border`, `-border-hover`, `-border-focus`,
147
+ `-ring`, `-text`, `-placeholder`, `-radius`) aliases the matching `--field-*`
148
+ tokens, so shadcn-shaped input code themes without renaming. **Author against
149
+ `--field-*`;** the `--input-*` names are only the alias bridge.
150
+
151
+ On a solid surface, read the foreground tokens (`--primary-foreground`,
152
+ `--destructive-foreground`, `--success-foreground`), which encode the mode-safe
153
+ inverse pair, instead of writing literal `#000` / `#fff`.
154
+
155
+ put.io components use the long descriptive names (`--component-bg-hover`,
158
156
  `--yellow-text-secondary`) because they name the role exactly; shadcn-imported
159
157
  code keeps the short names.
160
158
 
@@ -162,31 +160,32 @@ code keeps the short names.
162
160
 
163
161
  Base UI / headless primitives set
164
162
  `data-state="open|closed|checked|unchecked|active|completed|pending|loading"`
165
- on the DOM. Our components target those same attribute values in CSS; see the
166
- wizard step bar in `web-p00b-form-flows.html`
167
- (`.step[data-state="completed"]`) and the menu/table cards. Prefer `data-state`
168
- over inventing class flags (`.is-active`, `.on`, `.current`) so the same DOM
169
- contract works with any controlling code and devtools show the state directly.
163
+ on the DOM. put.io components target those same attribute values in CSS; see
164
+ the wizard step bar in `web-p00b-form-flows.html`
165
+ (`.step[data-state="completed"]`) and the menu/table cards. Express state with
166
+ `data-state` rather than class flags (`.is-active`, `.on`, `.current`), so the
167
+ same DOM contract works with any controlling code and devtools show the state
168
+ directly.
170
169
 
171
170
  ### Compound parts via `data-slot`
172
171
 
173
172
  shadcn marks parts of compound components with `data-slot="card-header"`,
174
- `data-slot="card-title"`, etc. We do the same on the folder picker
173
+ `data-slot="card-title"`, and so on. put.io does the same on the folder picker
175
174
  (`data-slot="modal" / "modal-header" / "modal-body" / "modal-footer"`) and the
176
175
  auth cards in `web-p00c-auth-signin.html` and `web-p00d-auth-signup.html`
177
- (`data-slot="auth" / "auth-header" / "auth-body"`). This lets external CSS tweak one part without inventing
178
- nested class chains.
176
+ (`data-slot="auth" / "auth-header" / "auth-body"`), so external CSS can target
177
+ one part without nested class chains.
179
178
 
180
179
  ## Design variants
181
180
 
182
181
  Four documented type stacks; pick one per surface:
183
182
 
184
- | Variant | Stack | Use when |
185
- |---------|-------|----------|
186
- | Clean Modern | GT America | default app UI |
187
- | Monospace | Berkeley Mono | terminal, logs, technical dashboards |
188
- | Brutalist | GT America Black + Berkeley Mono | marketing punctuation |
189
- | Editorial | GT America Black 900 (tight tracking) + GT America | landing hero, about |
183
+ | Variant | Stack | Use when |
184
+ | ------------ | -------------------------------------------------- | ------------------------------------ |
185
+ | Clean Modern | GT America | default app UI |
186
+ | Monospace | Berkeley Mono | terminal, logs, technical dashboards |
187
+ | Brutalist | GT America Black + Berkeley Mono | marketing punctuation |
188
+ | Editorial | GT America Black 900 (tight tracking) + GT America | landing hero, about |
190
189
 
191
190
  ## Preview cards
192
191
 
@@ -123,7 +123,7 @@
123
123
  "mono": {
124
124
  "$type": "fontFamily",
125
125
  "$value": "\"Berkeley Mono\", monospace",
126
- "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only — TV has no mono face.",
126
+ "$description": "The single mono face: numerics, timestamps, file sizes, ETAs, counts, code, logs, magnet URIs, identifiers, and filenames. Web and mobile only. TV product UI has no mono face; a TV debug overlay may use the platform's system monospace.",
127
127
  "$extensions": {
128
128
  "putio": {
129
129
  "cssName": "font-mono",