@putdotio/design 3.2.2 → 3.4.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
@@ -70,13 +70,13 @@ The canonical source for tokens is DTCG-compatible JSON in `tokens/`. Generated
70
70
 
71
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).
72
72
 
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.
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.
78
78
 
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.
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.
80
80
 
81
81
  ## Colors
82
82
 
@@ -88,9 +88,9 @@ Dark app surfaces are the default product mode, but the token contract includes
88
88
 
89
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
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.
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
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 — `"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.
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
94
 
95
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
96
 
@@ -102,7 +102,7 @@ put.io layouts are file-browser layouts first: lists, rows, columns, sidebars, t
102
102
 
103
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
104
 
105
- Stacking uses the token scale — `--z-dropdown` (1000), `--z-sticky` (1100), `--z-modal` (1300), `--z-notification` (1400), `--z-tooltip` (1500) — gapped for platform chrome; 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.
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.
106
106
 
107
107
  ## Elevation & Depth
108
108
 
@@ -112,25 +112,19 @@ Elevation should clarify interaction, not decorate. Use borders and soft shadows
112
112
 
113
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
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.
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.
116
116
 
117
117
  ## Components
118
118
 
119
- The component recipes below and in `system/components.css` are the **web
120
- binding** of the token graph, and they bind the web surfaces in full. The other
121
- tiers inherit differently: native apps (SwiftUI, Compose) consume the token
122
- *values* and build every component from their platform's human interface
123
- guidelines — stock controls themed with these tokens, never re-drawn web
124
- recipes (first adopter: `DESIGN.md` in
125
- [putio-ios](https://github.com/putdotio/putio-ios)); Roku inherits the tokens
126
- with more room for put.io conventions in custom SceneGraph components; the
127
- web-based TV app applies this system restrained to the product's generic
128
- list-first 10-foot interface, which every TV surface — including the native
129
- ones, within their platform conventions — treats as the shared family look.
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.
130
124
 
131
- 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.
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.
132
126
 
133
- 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.
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.
134
128
 
135
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`.
136
130
 
@@ -181,9 +175,9 @@ to narrow Auth cards without horizontal scrolling.
181
175
 
182
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.
183
177
 
184
- 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 buttons step both their fill and their border. 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.
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 buttons step both their fill and their border. 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.
185
179
 
186
- The 10-foot scale lives in the `tv` token group — type steps, spacing ramp, overscan ratios, radius, and z-indices — 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.
180
+ The 10-foot scale (type steps, spacing ramp, overscan ratios, radius, 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.
187
181
 
188
182
  ## Do's and Don'ts
189
183
 
@@ -193,6 +187,6 @@ Do use Phosphor-style inline SVG for forward-looking icon guidance and no emoji
193
187
 
194
188
  Do keep `DESIGN.md`, `tokens/`, `dist/`, and `system/tokens.css` in agreement.
195
189
 
196
- Don't publish private material in this public repo — the enumerated list lives in the repo's `docs/DISTRIBUTION.md` (Public Safety).
190
+ Don't publish private material in this public repo; the enumerated list lives in the repo's `docs/DISTRIBUTION.md` (Public Safety).
197
191
 
198
- 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`.
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`.
package/README.md CHANGED
@@ -24,13 +24,14 @@ static guidance.
24
24
 
25
25
  ## Install
26
26
 
27
- Token sources live in [`tokens`](tokens). Generated package artifacts live in
28
- [`dist`](dist). Package-safe brand assets live in [`system/assets`](system/assets).
29
-
30
27
  ```bash
31
28
  npm install @putdotio/design
32
29
  ```
33
30
 
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
+
34
35
  ## Use
35
36
 
36
37
  Import the generated tokens first. Tier-1 web surfaces also import the
@@ -41,30 +42,21 @@ component recipes:
41
42
  @import "@putdotio/design/components";
42
43
  ```
43
44
 
44
- Component recipes should consume semantic aliases such as `--field-*`,
45
- `--panel-*`, `--primary`, `--success`, and `--destructive` instead of reaching
46
- into palette tokens directly.
47
-
48
- Package entrypoints:
45
+ Component recipes consume semantic aliases such as `--field-*`, `--panel-*`,
46
+ `--primary`, `--success`, and `--destructive`, not palette tokens.
49
47
 
50
- - CSS custom properties: [`dist/css/tokens.css`](dist/css/tokens.css)
51
- - Tier-1 component recipes: [`system/components.css`](system/components.css)
52
- - DTCG token tree: [`dist/tokens.dtcg.json`](dist/tokens.dtcg.json)
53
- - Flat token metadata: [`dist/tokens.flat.json`](dist/tokens.flat.json)
54
- - Figma-safe subset: [`dist/figma/putio.tokens.json`](dist/figma/putio.tokens.json)
55
- - Brand assets: [`system/assets`](system/assets) via `@putdotio/design/assets/<file>`
56
- - Design contract: [`DESIGN.md`](DESIGN.md)
48
+ Package entrypoints, from `exports` in [package.json](package.json):
57
49
 
58
- ## Commands
59
-
60
- ```bash
61
- pnpm install
62
- pnpm dev
63
- pnpm verify
64
- pnpm verify:full
65
- ```
66
-
67
- Use `pnpm tokens:build` after token edits.
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) |
68
60
 
69
61
  ## Docs
70
62
 
@@ -72,14 +64,9 @@ Use `pnpm tokens:build` after token edits.
72
64
  - [Design guide structure](system/README.md)
73
65
  - [Distribution](docs/DISTRIBUTION.md)
74
66
  - [Contributing](CONTRIBUTING.md)
75
- - [Security](SECURITY.md)
67
+ - [Security](https://github.com/putdotio/.github/blob/main/SECURITY.md)
76
68
  - [Agent guide](AGENTS.md)
77
69
 
78
- ## Contributing
79
-
80
- See [Contributing](CONTRIBUTING.md) for local setup, validation, and pull
81
- request expectations.
82
-
83
70
  ## License
84
71
 
85
72
  [MIT](LICENSE)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/design",
3
- "version": "3.2.2",
3
+ "version": "3.4.0",
4
4
  "description": "Public put.io design tokens, brand assets, and design-system guidance.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,15 +68,12 @@
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
- "design:mirror": "node scripts/export-design-mirror.ts",
72
71
  "test:config": "node --test tests/playwright-config.test.mjs",
73
- "test:mirror": "node --test tests/export-design-mirror.test.mjs",
74
72
  "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",
75
73
  "html:check": "html-validate \"system/**/*.html\"",
76
- "skills:lint": "skillcheck lint",
77
74
  "e2e": "playwright test",
78
- "verify": "pnpm skills:lint && pnpm typecheck:scripts && pnpm tokens:check && pnpm html:check",
79
- "verify:full": "pnpm verify && pnpm test:mirror && pnpm test:config && pnpm e2e && npm pack --dry-run",
75
+ "verify": "pnpm typecheck:scripts && pnpm tokens:check && pnpm html:check",
76
+ "verify:full": "pnpm verify && pnpm test:config && pnpm e2e && npm pack --dry-run",
80
77
  "check": "pnpm verify",
81
78
  "deploy:production": "sst deploy --stage production"
82
79
  },
@@ -84,12 +81,11 @@
84
81
  "@axe-core/playwright": "^4.11.3",
85
82
  "@playwright/test": "^1.60.0",
86
83
  "@types/node": "^26.0.0",
87
- "@uinaf/skillcheck": "0.4.0",
88
84
  "html-validate": "^11.2.0",
89
85
  "sst": "^4.14.3",
90
86
  "style-dictionary": "^5.4.1",
91
87
  "typescript": "^7.0.2",
92
88
  "yaml": "^2.9.0"
93
89
  },
94
- "packageManager": "pnpm@12.0.0"
90
+ "packageManager": "pnpm@12.4.2"
95
91
  }
package/system/README.md CHANGED
@@ -4,42 +4,37 @@ This folder holds the generated site tokens, preview cards, and design-system in
4
4
 
5
5
  ## Layout
6
6
 
7
- ```
8
- project root/
9
- ├── platforms/ ← per-platform binding contracts (DESIGN.md each)
10
- └── system/
11
- ├── index.html ← redirects to the design-system guide
12
- ├── design-system.html ← guide and preview-card index
13
- ├── design-system-light.html ← legacy redirect to design-system.html?theme=light
14
- ├── tokens.css ← generated from ../tokens/**/*.tokens.json
15
- ├── components.css ← the tier-1 component layer (the web contract)
16
- ├── tv.css ← TV / 10ft component CSS, scoped to .tv artboards
17
- ├── assets/ ← logos, favicons, retro marks, app icons
18
- └── preview/ ← specimen cards for foundations + every platform tier
19
- └── _*.css, _resize.js ← card chrome only (frame, tier strip, HIG/M3/Roku shells)
20
- ```
21
-
22
- Cards are named `<platform>-<section><n>-<subject>.html` — section letter `f`
23
- foundations, `c` components, `e` elements, `p` patterns, `s` screens
7
+ - `index.html` redirects to `design-system.html`, the guide and preview-card
8
+ index. `design-system-light.html` is a legacy redirect to
9
+ `design-system.html?theme=light`.
10
+ - `tokens.css` is generated from `../tokens/**/*.tokens.json`; do not edit it.
11
+ - `components.css` is the Tier-1 component layer (the web contract).
12
+ - `tv.css` is the TV / 10-foot component CSS, scoped to `.tv` artboards.
13
+ - `assets/` holds logos, favicons, retro marks, and app icons.
14
+ - `preview/` holds specimen cards for the foundations and every platform tier.
15
+ Its `_`-prefixed files are card chrome only (frame, tier strip, HIG, M3, and
16
+ Roku shells, resize and password-toggle helpers).
17
+
18
+ Cards are named `<platform>-<section><n>-<subject>.html` with section letter `f`
19
+ foundations, `c` components, `e` elements, `p` patterns, or `s` screens
24
20
  (`ios-e01-toggle.html`); the numbered `00-…13-…` files are the tier-0
25
21
  foundations. Element cards are a fixed 1120×580 viewport: one control, its
26
22
  states, a spec strip, a don't block.
27
23
 
28
24
  ## Fonts
29
25
 
30
- Preview pages load the public put.io font CSS from `static.put.io` (the same
31
- source as the product), so the design system always matches production. Do not
32
- commit font files here. Two families, so each HTML file links two stylesheets in
33
- `<head>`:
26
+ 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>`:
34
29
 
35
30
  ```html
36
31
  <link rel="stylesheet" href="https://static.put.io/fonts/gt-america/standard/font.css">
37
32
  <link rel="stylesheet" href="https://static.put.io/fonts/berkeley-mono/variable/font.css">
38
33
  ```
39
34
 
40
- GT America Mono is retired. `--font-ui-mono` no longer exists and everything mono
41
- reads `--font-mono` (Berkeley Mono), whose tabular figures carry the numerics the
42
- UI mono used to. Do not re-add the `gt-america/mono/font.css` load.
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.
43
38
 
44
39
  Canonical weight mapping (matches the brand font host):
45
40
  `100 ultra-light · 200 thin · 300 light · 400 regular · 500 medium · 700 bold · 900 black`
@@ -56,7 +51,7 @@ The canonical source is DTCG-compatible JSON in [`../tokens`](../tokens). This
56
51
  site stylesheet (`system/tokens.css`) and the package stylesheet
57
52
  (`dist/css/tokens.css`, exported as `@putdotio/design/css`) are generated from
58
53
  the same source. Guide and component CSS should consume custom properties rather
59
- than hard-coded palette values. Brand yellow is `var(--yellow-solid)` —
54
+ than hard-coded palette values. Brand yellow is `var(--yellow-solid)`,
60
55
  canonical `#FDCE45`; the emitted hsl form is pinned in
61
56
  [`../DESIGN.md`](../DESIGN.md) front-matter, and the `--yellow-solid-hover` →
62
57
  `--button-primary-bg-hover` derivation is specified in the repo's
@@ -68,15 +63,9 @@ font-size is responsive; the canonical values live in
68
63
  rule: CSS media queries can't read custom properties, so write the literal px
69
64
  in `@media` and keep it in sync with the `--bp-*` token.
70
65
 
71
- Web package consumers import the generated tokens first. Tier-1 consumers also
72
- import the component recipes:
66
+ Package consumers import as in the [README](../README.md#use).
73
67
 
74
- ```css
75
- @import "@putdotio/design/css";
76
- @import "@putdotio/design/components";
77
- ```
78
-
79
- For **TV preview cards**, also load `tv.css` — it adds the `.tv`, `.scr`,
68
+ For **TV preview cards**, also load `tv.css`, which adds the `.tv`, `.scr`,
80
69
  `.row`, solid surface tiers, and player chrome on top of the tokens, authored
81
70
  1:1 at 1920×1080 (a px in the file is a px on the TV). Every component selector
82
71
  is scoped under `.tv`, so the file is safe to bundle globally without bleeding
@@ -88,52 +77,30 @@ native UI stacks:
88
77
  <link rel="stylesheet" href="./tv.css">
89
78
  ```
90
79
 
91
- TV surfaces are **solid token colors** — no `backdrop-filter`, no translucent
92
- white fills. Focus is a fill: rows go transparent → `--component-bg-active`,
93
- buttons go `--component-bg` → `--component-bg-active` with the border stepping
94
- to `--border-hover`. There is no scale, shadow, halo, or white invert.
95
-
96
- The 10-foot scale itself is graph data, not CSS. The `tv` token group
97
- (`--tv-text-*`, `--tv-space-*`, `--tv-overscan-*`,
98
- `--tv-radius`, `--tv-z-*`) carries the 1:1 values for native emitters and is
99
- `mode: "tv"`, so it is deliberately **not** emitted into `tokens.css` — read it
100
- from `dist/tokens.flat.json`. Only `--radius-tv` (12px, the single radius for
101
- every TV surface) is global and reachable in CSS. Two things must not be folded
102
- into the web scales: TV has no mono face, and TV orders `--tv-z-overlay` (300)
103
- *above* `--tv-z-toast` (200), the opposite of the web `--z-*` stack.
80
+ 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.
104
84
 
105
85
  ## Core rules
106
86
 
107
- - **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`).
108
- - **Yellow as text on light needs `--yellow-text-secondary`** (≈5:1, AA) — the brand `--yellow-solid` is fill-only on light backgrounds.
109
- - **Labels on yellow** use `--primary-foreground` (`hsl(38, 65%, 10%)` — warm dark, never pure black). Passes AA on the yellow fill without feeling "hard."
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."
110
90
  - **Icons: Phosphor-style inline SVG.** No emoji, ever.
111
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()`.
112
- - **Fields and panels use the shared aliases** (`--field-*`, `--panel-*`) — full rules, including the `aria-invalid` red-border-only requirement, in [`../DESIGN.md`](../DESIGN.md) (Components).
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).
113
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.
114
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.
115
95
 
116
96
  ## Binding tiers
117
97
 
118
- ADR 0009 (putio-frontend `docs/decisions/0009-design-binding-tiers.md`): **a
119
- web-rendered mock is never a native spec**. Every specimen card declares its
120
- tier in a strip above the artboard (`preview/_tier.css` — the only place the
121
- tier text lives besides `../DESIGN.md`; change both in the same PR).
122
-
123
- | Tier | Surfaces | What binds |
124
- | --- | --- | --- |
125
- | 0 | Foundations | The token graph. Binds all tiers, values only |
126
- | 1 | www · app · auth | The full system — `components.css` recipes are the contract, here and only here |
127
- | 2 | iOS · iPadOS · tvOS · watchOS · Android · Android TV | Tokens only; every control from the platform HIG / Material 3 |
128
- | 3 | Roku | Tier-2 token inheritance + put.io conventions in custom SceneGraph components |
129
- | 4 | tv.put.io · Tizen · webOS | The web system, restrained to a list-first 10-foot interface |
130
-
131
- A tier-1/3/4 card is a **Component** (put.io owns the recipe); a tier-2 card is
132
- an **Element** (the platform owns the control; the card documents how it is
133
- tokenized). Never read an Element card as a build-a-custom-control instruction.
134
-
135
- Native cards are drawn 1:1 in their own unit — iPhone 393×852pt, iPad
136
- 1024×768pt, Apple Watch 176×215pt, Android 412×915dp, TV 1920×1080px — so a
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.
101
+
102
+ Native cards are drawn 1:1 in their own unit (iPhone 393×852pt, iPad
103
+ 1024×768pt, Apple Watch 176×215pt, Android 412×915dp, TV 1920×1080px), so a
137
104
  number written on a card is the number in the HIG or the Material spec. Those
138
105
  metrics are Apple's and Google's; they are **not** put.io spacing tokens and
139
106
  must never be promoted into the graph. Device bezels and on-video chrome stay
@@ -147,25 +114,22 @@ The guide (`design-system.html`) owns the theme toggle. It writes
147
114
  so embeds flip live without reloading. Each preview's inline bootstrap reads
148
115
  the same storage key on standalone open, and `preview/_resize.js` listens for
149
116
  the parent broadcast when embedded. A URL query (`?theme=light` or
150
- `?theme=dark`) overrides both — useful for screenshots.
117
+ `?theme=dark`) overrides both, which is useful for screenshots.
151
118
 
152
119
  Fixed-mode previews opt out with `<html data-theme-lock="dark">`. This is only
153
- for product mockups whose visual language is intrinsically one mode — a 10-foot
120
+ for product mockups whose visual language is intrinsically one mode. A 10-foot
154
121
  screen, a native app mockup, and on-video player chrome are fixed-mode
155
122
  surfaces, so the `ios-*`, `android-*`, `androidtv-*`, `tvos-*`, `watchos-*`,
156
123
  `roku-*`, and `tv-*` cards plus `web-p06-player.html` are locked. Locked
157
124
  previews ignore both `localStorage` and parent broadcasts.
158
- If you write a new component preview, you almost certainly don't want the
159
- lock — let the tokens do the work and your specimen will render correctly in
160
- both modes.
125
+ New component previews should not use the lock; the tokens render them
126
+ correctly in both modes.
161
127
 
162
128
  ## shadcn / Base UI interop
163
129
 
164
- The token layer ships a name-alias bridge so external component libraries that
165
- read canonical shadcn token names theme correctly without renaming. Drop in a
166
- shadcn/ui block, install a Base UI component, paste a Tailwind preset — they
167
- resolve against our values via the aliases. Always alias, never duplicate, so
168
- the two name systems can't drift.
130
+ The token layer aliases the canonical shadcn token names, so shadcn/ui blocks,
131
+ 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.
169
133
 
170
134
  | shadcn name | put.io target |
171
135
  | --- | --- |
@@ -187,23 +151,22 @@ renaming. **Author against `--field-*`;** the `--input-*` names exist only as th
187
151
  alias bridge and must never carry their own value.
188
152
 
189
153
  The foreground tokens (`--primary-foreground`, `--destructive-foreground`,
190
- `--success-foreground`) encode the mode-safe inverse pair — always read these
154
+ `--success-foreground`) encode the mode-safe inverse pair; always read these
191
155
  instead of writing literal `#000` / `#fff` on a solid surface.
192
156
 
193
- Use whichever name reads cleaner in context. Our own components prefer the long
194
- descriptive names (`--component-bg-hover`, `--yellow-text-secondary`) because
195
- they describe the role exactly. shadcn-imported code prefers the short names.
157
+ put.io components prefer the long descriptive names (`--component-bg-hover`,
158
+ `--yellow-text-secondary`) because they name the role exactly; shadcn-imported
159
+ code keeps the short names.
196
160
 
197
161
  ### State via `data-state`, not class flags
198
162
 
199
163
  Base UI / headless primitives set
200
164
  `data-state="open|closed|checked|unchecked|active|completed|pending|loading"`
201
- on the DOM. Our components target those same attribute values in CSS — see the
165
+ on the DOM. Our components target those same attribute values in CSS; see the
202
166
  wizard step bar in `web-p00b-form-flows.html`
203
167
  (`.step[data-state="completed"]`) and the menu/table cards. Prefer `data-state`
204
168
  over inventing class flags (`.is-active`, `.on`, `.current`) so the same DOM
205
- contract works whether the controlling code is React, Vue, vanilla JS, or
206
- hand-written HTML, and so devtools can read the component's state at a glance.
169
+ contract works with any controlling code and devtools show the state directly.
207
170
 
208
171
  ### Compound parts via `data-slot`
209
172
 
@@ -216,7 +179,7 @@ nested class chains.
216
179
 
217
180
  ## Design variants
218
181
 
219
- Four documented type stacks — pick one per surface:
182
+ Four documented type stacks; pick one per surface:
220
183
 
221
184
  | Variant | Stack | Use when |
222
185
  |---------|-------|----------|
@@ -227,4 +190,5 @@ Four documented type stacks — pick one per surface:
227
190
 
228
191
  ## Preview cards
229
192
 
230
- Open `system/design-system.html` for the full index. Each card renders a single concept against the actual tokens — read it like a spec.
193
+ `design-system.html` is the full index. Each card renders one concept against
194
+ the real tokens; read it as a spec.