@ponchia/ui 0.7.0 → 0.8.1

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.
Files changed (54) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/README.md +3 -3
  3. package/classes/classes.json +79 -7
  4. package/classes/index.d.ts +53 -1
  5. package/classes/index.js +60 -0
  6. package/classes/vscode.css-custom-data.json +24 -0
  7. package/css/app.css +14 -3
  8. package/css/disclosure.css +7 -7
  9. package/css/feedback.css +11 -8
  10. package/css/forms.css +3 -3
  11. package/css/navigation.css +1 -1
  12. package/css/overlay.css +19 -5
  13. package/css/primitives.css +101 -6
  14. package/css/site.css +11 -5
  15. package/css/skins.css +97 -3
  16. package/css/state.css +161 -0
  17. package/css/table.css +1 -1
  18. package/css/tokens.css +6 -0
  19. package/css/workbench.css +151 -0
  20. package/dist/bronto.css +1 -1
  21. package/dist/css/app.css +1 -1
  22. package/dist/css/disclosure.css +1 -1
  23. package/dist/css/feedback.css +1 -1
  24. package/dist/css/forms.css +1 -1
  25. package/dist/css/navigation.css +1 -1
  26. package/dist/css/overlay.css +1 -1
  27. package/dist/css/primitives.css +1 -1
  28. package/dist/css/report-kit.css +1 -1
  29. package/dist/css/site.css +1 -1
  30. package/dist/css/skins.css +1 -1
  31. package/dist/css/state.css +1 -1
  32. package/dist/css/table.css +1 -1
  33. package/dist/css/tokens.css +1 -1
  34. package/dist/css/workbench.css +1 -1
  35. package/docs/adr/0001-color-system.md +32 -3
  36. package/docs/contrast.md +102 -18
  37. package/docs/migrations/0.7-to-0.8.md +216 -0
  38. package/docs/package-contract.md +1 -0
  39. package/docs/reference.md +52 -1
  40. package/docs/reporting.md +8 -8
  41. package/docs/stability.md +31 -2
  42. package/docs/state.md +51 -1
  43. package/docs/theming.md +18 -0
  44. package/docs/usage.md +62 -2
  45. package/docs/workbench.md +83 -4
  46. package/llms.txt +1 -1
  47. package/package.json +13 -12
  48. package/tokens/figma.variables.json +84 -0
  49. package/tokens/index.d.ts +2 -2
  50. package/tokens/index.js +23 -0
  51. package/tokens/index.json +12 -0
  52. package/tokens/resolved.json +6 -0
  53. package/tokens/skins.js +117 -7
  54. package/tokens/tokens.dtcg.json +7 -1
@@ -1 +1 @@
1
- @layer bronto{.ui-inspector{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;gap: var(--space-sm);padding: var(--space-md)}.ui-inspector__head{align-items: baseline;border-block-end: 1px solid var(--line);display: flex;gap: var(--space-sm);justify-content: space-between;padding-block-end: var(--space-xs)}.ui-inspector__body{display: grid;gap: var(--space-2xs)}.ui-toolstrip{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);display: flex;flex-wrap: wrap;gap: var(--space-xs);min-inline-size: 0;padding: 0.35rem}.ui-toolstrip--floating{box-shadow: var(--shadow-raised)}.ui-toolstrip--compact{gap: var(--space-2xs);padding: 0.25rem}.ui-toolstrip__brand{align-items: baseline;display: inline-flex;flex: 0 1 auto;gap: var(--space-xs);min-inline-size: 0;padding-inline: var(--space-xs)}.ui-toolstrip__brand strong{color: var(--text);font-size: var(--text-sm)}.ui-toolstrip__context{color: var(--text-dim);font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__group,.ui-toolstrip__actions{align-items: center;display: inline-flex;flex-wrap: wrap;gap: var(--space-2xs)}.ui-toolstrip__group{background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);padding: 0.16rem}.ui-toolstrip__search{flex: 1 1 14rem;min-inline-size: min(100%,11rem)}.ui-toolstrip__actions{margin-inline-start: auto}.ui-segmented-buttons{align-items: center;background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);display: inline-flex;flex-wrap: wrap;gap: 0.16rem;padding: 0.16rem}.ui-segmented-buttons__button{background: transparent;border: 1px solid transparent;border-radius: var(--radius-sm);color: var(--text-soft);cursor: pointer;font-family: var(--mono);font-size: var(--text-xs);font-weight: 700;letter-spacing: var(--tracking-wide);line-height: 1;min-block-size: 1.9rem;padding: 0.48rem 0.7rem;text-transform: uppercase}.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: var(--accent);border-color: var(--accent);color: var(--button-text)}.ui-segmented-buttons__button:disabled,.ui-segmented-buttons__button[aria-disabled='true']{cursor: not-allowed;opacity: 0.45}.ui-property{align-items: baseline;display: grid;gap: var(--space-sm);grid-template-columns: minmax(6rem,38%) 1fr}.ui-property__label{color: var(--text-dim);font-family: var(--mono);font-size: var(--text-2xs);letter-spacing: var(--tracking-wide);text-transform: uppercase}.ui-property__value{color: var(--text);font-size: var(--text-sm);min-inline-size: 0}.ui-selectionbar{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);box-shadow: var(--shadow-raised);display: flex;flex-wrap: wrap;gap: var(--space-sm) var(--space-md);justify-content: space-between;padding: 0.5rem 0.85rem}.ui-selectionbar__count{color: var(--text);font-family: var(--mono);font-size: var(--text-sm)}.ui-selectionbar__actions{display: flex;flex-wrap: wrap;gap: var(--space-xs)}@media (hover: hover){.ui-segmented-buttons__button:hover:not(:disabled,[aria-disabled='true'],[aria-pressed='true'],.is-active){background: var(--panel);border-color: var(--line-strong);color: var(--text)}}.ui-splitter{--splitter-pos: 50%;--splitter-handle: 0.75rem;--splitter-pane-min: 0px;background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr);min-block-size: 18rem;overflow: hidden}.ui-splitter--vertical{grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter--horizontal{grid-template-columns: 1fr;grid-template-rows: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter__pane{min-block-size: 0;min-inline-size: 0;overflow: auto;padding: var(--space-md)}.ui-splitter__handle{align-self: stretch;background: var(--panel-strong);border-block: 0;border-inline: 1px solid var(--line);cursor: col-resize;display: grid;min-inline-size: var(--splitter-handle);place-items: center;touch-action: none}.ui-splitter__handle::before{background: var(--line-strong);border-radius: var(--radius-pill);block-size: 2.5rem;content: "";inline-size: 2px}.ui-splitter__handle:hover::before,.ui-splitter__handle.is-active::before,.ui-splitter__handle[aria-valuenow]:focus-visible::before{background: var(--accent)}.ui-splitter__handle:focus-visible{outline: 2px solid var(--focus-ring);outline-offset: -2px}.ui-splitter--horizontal .ui-splitter__handle{border-block: 1px solid var(--line);border-inline: 0;cursor: row-resize;min-block-size: var(--splitter-handle);min-inline-size: 0}.ui-splitter--horizontal .ui-splitter__handle::before{block-size: 2px;inline-size: 2.5rem}@media (forced-colors: active){.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: Highlight;border-color: Highlight;color: HighlightText}.ui-splitter__handle{border-color: CanvasText}.ui-splitter__handle::before{background: CanvasText}}}
1
+ @layer bronto{.ui-inspector{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;gap: var(--space-sm);padding: var(--space-md)}.ui-inspector__head{align-items: baseline;border-block-end: 1px solid var(--line);display: flex;gap: var(--space-sm);justify-content: space-between;padding-block-end: var(--space-xs)}.ui-inspector__body{display: grid;gap: var(--space-2xs)}.ui-toolstrip{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);display: flex;flex-wrap: wrap;gap: var(--space-xs);min-inline-size: 0;padding: 0.35rem}.ui-toolstrip--floating{box-shadow: var(--shadow-raised)}.ui-toolstrip--compact{gap: var(--space-2xs);padding: 0.25rem}.ui-toolstrip--pane{background: none;border: 0;border-block-end: 1px solid var(--line);border-radius: 0;flex-wrap: nowrap;gap: var(--space-2xs);overflow-x: auto;padding: 0.3rem var(--space-xs);scrollbar-width: thin}.ui-toolstrip__fill{flex: 1 1 auto;min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__brand{align-items: baseline;display: inline-flex;flex: 0 1 auto;gap: var(--space-xs);min-inline-size: 0;padding-inline: var(--space-xs)}.ui-toolstrip__brand strong{color: var(--text);font-size: var(--text-sm)}.ui-toolstrip__context{color: var(--text-dim);font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-toolstrip__group,.ui-toolstrip__actions{align-items: center;display: inline-flex;flex-wrap: wrap;gap: var(--space-2xs)}.ui-toolstrip__group{background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);padding: 0.16rem}.ui-toolstrip__search{flex: 1 1 14rem;min-inline-size: min(100%,11rem)}.ui-toolstrip__actions{margin-inline-start: auto}.ui-segmented-buttons{align-items: center;background: var(--panel-soft);border: 1px solid var(--line);border-radius: var(--radius-md);display: inline-flex;flex-wrap: wrap;gap: 0.16rem;padding: 0.16rem}.ui-segmented-buttons__button{background: transparent;border: 1px solid transparent;border-radius: var(--radius-sm);color: var(--text-soft);cursor: pointer;font-family: var(--mono);font-size: var(--text-xs);font-weight: 700;letter-spacing: var(--tracking-wide);line-height: 1;min-block-size: 1.9rem;padding: 0.48rem 0.7rem;text-transform: uppercase}.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: var(--accent);border-color: var(--accent);color: var(--button-text)}.ui-segmented-buttons__button:disabled,.ui-segmented-buttons__button[aria-disabled='true']{cursor: not-allowed;opacity: 0.45}.ui-property{align-items: baseline;display: grid;gap: var(--space-sm);grid-template-columns: minmax(6rem,38%) 1fr}.ui-property__label{color: var(--text-dim);font-family: var(--mono);font-size: var(--text-2xs);letter-spacing: var(--tracking-wide);text-transform: uppercase}.ui-property__value{color: var(--text);font-size: var(--text-sm);min-inline-size: 0}.ui-pane{background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: flex;flex-direction: column;min-block-size: 0;min-inline-size: 0;overflow: hidden}.ui-pane__head{align-items: center;background: var(--bg-elevated);border-block-end: 1px solid var(--line);cursor: grab;display: flex;flex: none;gap: var(--space-2xs);min-block-size: var(--pane-head,2.5rem);padding: var(--space-2xs) var(--space-xs)}.ui-pane__head:active{cursor: grabbing}.ui-pane__title{flex: 1 1 auto;font-size: var(--text-xs);min-inline-size: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap}.ui-pane__title-input{background: var(--bg-elevated);border: 1px solid var(--accent);border-radius: var(--radius-sm);color: inherit;font: inherit;letter-spacing: inherit;margin-block: -1px;max-inline-size: 100%;min-inline-size: 0;padding: 0 0.25rem}.ui-pane__actions{display: flex;flex: 0 1 auto;flex-wrap: nowrap;gap: var(--space-2xs);min-inline-size: 0;overflow-x: auto;scrollbar-width: thin}.ui-pane__actions:empty{display: none}.ui-pane__body{display: flex;flex: 1;flex-direction: column;min-block-size: 0;min-inline-size: 0;overflow: auto}.ui-selectionbar{align-items: center;background: var(--panel-strong);border: 1px solid var(--line-strong);border-radius: var(--radius-md);box-shadow: var(--shadow-raised);display: flex;flex-wrap: wrap;gap: var(--space-sm) var(--space-md);justify-content: space-between;padding: 0.5rem 0.85rem}.ui-selectionbar__count{color: var(--text);font-family: var(--mono);font-size: var(--text-sm)}.ui-selectionbar__actions{display: flex;flex-wrap: wrap;gap: var(--space-xs)}.ui-selectionbar--anchored,.ui-toolstrip--anchored{--anchor-offset: var(--space-sm);inset-inline-start: 50%;max-inline-size: calc(100% - 2 * var(--space-md));position: absolute;transform: translateX(-50%)}.ui-selectionbar--anchored,.ui-toolstrip--anchored,.ui-selectionbar--anchor-block-end,.ui-toolstrip--anchor-block-end{inset-block: auto max(var(--anchor-offset),var(--safe-area-bottom))}.ui-selectionbar--anchor-block-start,.ui-toolstrip--anchor-block-start{inset-block: max(var(--anchor-offset),var(--safe-area-top)) auto}@media (hover: hover){.ui-segmented-buttons__button:hover:not(:disabled,[aria-disabled='true'],[aria-pressed='true'],.is-active){background: var(--panel);border-color: var(--line-strong);color: var(--text)}}.ui-splitter{--splitter-pos: 50%;--splitter-handle: 0.75rem;--splitter-pane-min: 0px;background: var(--panel);border: 1px solid var(--line);border-radius: var(--radius-md);display: grid;grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr);min-block-size: 18rem;overflow: hidden}.ui-splitter--vertical{grid-template-columns: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter--horizontal{grid-template-columns: 1fr;grid-template-rows: minmax(var(--splitter-pane-min),var(--splitter-pos)) var(--splitter-handle) minmax(0,1fr)}.ui-splitter__pane{min-block-size: 0;min-inline-size: 0;overflow: auto;padding: var(--space-md)}.ui-splitter__handle{align-self: stretch;background: var(--panel-strong);border-block: 0;border-inline: 1px solid var(--line);cursor: col-resize;display: grid;min-inline-size: var(--splitter-handle);place-items: center;touch-action: none}.ui-splitter__handle::before{background: var(--line-strong);border-radius: var(--radius-pill);block-size: 2.5rem;content: "";inline-size: 2px}.ui-splitter__handle:hover::before,.ui-splitter__handle.is-active::before,.ui-splitter__handle[aria-valuenow]:focus-visible::before{background: var(--accent)}.ui-splitter__handle:focus-visible{outline: 2px solid var(--focus-ring);outline-offset: -2px}.ui-splitter--horizontal .ui-splitter__handle{border-block: 1px solid var(--line);border-inline: 0;cursor: row-resize;min-block-size: var(--splitter-handle);min-inline-size: 0}.ui-splitter--horizontal .ui-splitter__handle::before{block-size: 2px;inline-size: 2.5rem}@media (forced-colors: active){.ui-segmented-buttons__button[aria-pressed='true'],.ui-segmented-buttons__button.is-active{background: Highlight;border-color: Highlight;color: HighlightText}.ui-splitter__handle{border-color: CanvasText}.ui-splitter__handle::before{background: CanvasText}}}
@@ -165,13 +165,42 @@ Hard acceptance criteria for every step below:
165
165
  var(--accent) …)` declared on `:root` and only re-evaluates against the new
166
166
  accent on the element that carries it; a skin on a subtree would leave that
167
167
  family stale, so the selectors are `:root`-anchored and a subtree skin
168
- no-ops. Each skin re-points only `--accent` (+ a dark `--dotmatrix-glow`);
169
- the family + dot-matrix + glyphs follow automatically. Single-hue per skin →
168
+ no-ops. Each skin re-points `--accent` (+ a dark `--dotmatrix-glow`); the
169
+ family + dot-matrix + glyphs follow automatically. Single-hue per skin →
170
170
  keeps the one-accent discipline while unlocking range across skins. No
171
171
  multi-hue palette. (A future per-subtree skin would need to re-declare the
172
172
  derived family — deferred; whole-page is the natural colorway scope.) Skins
173
173
  inherit the existing `forced-colors` + `print` behavior in `css/base.css` /
174
- `css/forms.css` (they touch only the accent, never the canvas).
174
+ `css/forms.css`.
175
+
176
+ **Amended in 0.8.0 — a colorway also owns the neutral canvas.** The original
177
+ rule was accent-only, on the reasoning that the canvas is identity and the
178
+ accent is expression. A downstream consumer disproved it in the most direct
179
+ way available: offering these three as pickable looks, it found that
180
+ "Amber CRT" left the surface grey, and shipped a hand-written amber canvas of
181
+ its own — raw hex, dark theme only, outside OKLCH, outside `check-contrast`,
182
+ with `e-ink` silently un-tinted because a partial canvas has no failing edge.
183
+ Every one of those defects is a consequence of the rule, not of the consumer.
184
+ A look named after a phosphor display that renders on neutral grey is not a
185
+ colorway; it is an accent swatch with a colorway's name.
186
+
187
+ The amendment keeps the discipline that made the original rule right:
188
+
189
+ - The canvas is **derived, not picked**. Each of the ten neutrals
190
+ (`SKIN_CANVAS_TOKENS` in `tokens/skins.js`) keeps the core token's OKLCH
191
+ *lightness* exactly, adopts the skin accent's *hue*, and takes a small
192
+ role-scaled chroma. Contrast tracks relative luminance, which OKLCH L
193
+ tracks closely, so holding L fixed holds every pairing's ratio.
194
+ - It is **proven, not asserted**. `gen-contrast.mjs` widens a canvas-moving
195
+ skin's audit from the accent subset to the *full* 24-pair table, so all 21
196
+ gated pairings are re-measured per skin per theme on every run.
197
+ - The canvas is **all-or-nothing**, in both directions: `check-skins.mjs`
198
+ rejects a skin that re-points some neutrals but not others, and one that
199
+ re-points them in one theme but not the other. Those are exactly the two
200
+ shapes the consumer's hand-written version had.
201
+ - **Status colours stay locked.** They are not in `SKIN_CANVAS_TOKENS`: a
202
+ warning must look like a warning in every skin. Deriving status from a
203
+ colorway remains rejected (see Rejected alternatives).
175
204
  5. **OKLCH for new work first.** *(done for colorways — accents authored in
176
205
  OKLCH in `tokens/skins.js`; the contrast tooling now parses `oklch()` →
177
206
  sRGB, so skin accents are gated, not eyeballed.)* The core ramp followed in
package/docs/contrast.md CHANGED
@@ -111,36 +111,73 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
111
111
 
112
112
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
113
113
  | --- | --- | --- | --- | --- | --- | --- |
114
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.75:1 | Lc 78.9 | ✅ pass |
114
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.97:1 | Lc 99.0 | ✅ pass |
115
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.84:1 | Lc 105.8 | ✅ pass |
116
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.77:1 | Lc 94.4 | ✅ pass |
117
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.20:1 | Lc 91.3 | ✅ pass |
118
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.37:1 | Lc 98.1 | ✅ pass |
119
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.45:1 | Lc 86.8 | ✅ pass |
120
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.11:1 | Lc 71.4 | ✅ pass |
121
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.64:1 | Lc 78.2 | ✅ pass |
122
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.77:1 | Lc 66.9 | ✅ pass |
123
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.73:1 | Lc 78.7 | ✅ pass |
115
124
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 7.44:1 | Lc 85.6 | ✅ pass |
116
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.13:1 | Lc 78.7 | ✅ pass |
125
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.19:1 | Lc 78.7 | ✅ pass |
117
126
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 6.48:1 | Lc 76.1 | ℹ️ not gated |
118
127
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 6.85:1 | Lc 79.9 | ℹ️ not gated |
119
128
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.66:1 | Lc 83.3 | ✅ pass |
120
129
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 5.66:1 | Lc 83.3 | ✅ pass |
121
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.14:1 | Lc 71.4 | ✅ pass |
130
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.13:1 | Lc 71.2 | ✅ pass |
122
131
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.66:1 | Lc 78.0 | ✅ pass |
123
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.14:1 | Lc 71.4 | ✅ pass |
132
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.13:1 | Lc 71.2 | ✅ pass |
133
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
134
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
135
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
136
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
137
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.41:1 | Lc 47.4 | ℹ️ not gated |
124
138
 
125
139
  ### Amber CRT — dark
126
140
 
127
141
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
128
142
  | --- | --- | --- | --- | --- | --- | --- |
143
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.01:1 | Lc 91.0 | ✅ pass |
144
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.64:1 | Lc 90.0 | ✅ pass |
145
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.43:1 | Lc 88.9 | ✅ pass |
146
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.19:1 | Lc 72.7 | ✅ pass |
147
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.18:1 | Lc 71.7 | ✅ pass |
148
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.27:1 | Lc 70.5 | ✅ pass |
149
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.16:1 | Lc 50.2 | ✅ pass |
150
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.51:1 | Lc 49.2 | ✅ pass |
151
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 5.93:1 | Lc 48.1 | ✅ pass |
129
152
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 11.84:1 | Lc 76.1 | ✅ pass |
130
153
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 10.77:1 | Lc 75.1 | ✅ pass |
131
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.83:1 | Lc 65.5 | ✅ pass |
154
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.82:1 | Lc 65.4 | ✅ pass |
132
155
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.46:1 | Lc 20.8 | ℹ️ not gated |
133
156
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.51:1 | Lc 23.2 | ℹ️ not gated |
134
157
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 11.88:1 | Lc 71.6 | ✅ pass |
135
158
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 11.88:1 | Lc 71.6 | ✅ pass |
136
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.60:1 | Lc 69.9 | ✅ pass |
159
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.60:1 | Lc 69.8 | ✅ pass |
137
160
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 9.64:1 | Lc 68.9 | ✅ pass |
138
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.60:1 | Lc 69.9 | ✅ pass |
161
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.60:1 | Lc 69.8 | ✅ pass |
162
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
163
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
164
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
165
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
166
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.28:1 | Lc 14.5 | ℹ️ not gated |
139
167
 
140
168
  ### E-ink — light
141
169
 
142
170
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
143
171
  | --- | --- | --- | --- | --- | --- | --- |
172
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 17.98:1 | Lc 99.2 | ✅ pass |
173
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.79:1 | Lc 105.8 | ✅ pass |
174
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.73:1 | Lc 94.5 | ✅ pass |
175
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.16:1 | Lc 91.4 | ✅ pass |
176
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.29:1 | Lc 98.0 | ✅ pass |
177
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.39:1 | Lc 86.7 | ✅ pass |
178
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.09:1 | Lc 71.4 | ✅ pass |
179
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.61:1 | Lc 78.0 | ✅ pass |
180
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.74:1 | Lc 66.7 | ✅ pass |
144
181
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.23:1 | Lc 93.3 | ✅ pass |
145
182
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.47:1 | Lc 99.9 | ✅ pass |
146
183
  | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 8.78:1 | Lc 76.3 | ✅ pass |
@@ -151,51 +188,98 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
151
188
  | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.66:1 | Lc 90.4 | ✅ pass |
152
189
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.74:1 | Lc 97.0 | ✅ pass |
153
190
  | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.66:1 | Lc 90.4 | ✅ pass |
191
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
192
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
193
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
194
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
195
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.39:1 | Lc 47.1 | ℹ️ not gated |
154
196
 
155
197
  ### E-ink — dark
156
198
 
157
199
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
158
200
  | --- | --- | --- | --- | --- | --- | --- |
201
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.02:1 | Lc 91.1 | ✅ pass |
202
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.66:1 | Lc 90.1 | ✅ pass |
203
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.45:1 | Lc 89.0 | ✅ pass |
204
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.20:1 | Lc 72.8 | ✅ pass |
205
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.19:1 | Lc 71.8 | ✅ pass |
206
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.29:1 | Lc 70.6 | ✅ pass |
207
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.17:1 | Lc 50.3 | ✅ pass |
208
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.52:1 | Lc 49.3 | ✅ pass |
209
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 5.94:1 | Lc 48.2 | ✅ pass |
159
210
  | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.72:1 | Lc 80.3 | ✅ pass |
160
211
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.57:1 | Lc 79.3 | ✅ pass |
161
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.66:1 | Lc 65.0 | ✅ pass |
212
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.67:1 | Lc 65.0 | ✅ pass |
162
213
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.38:1 | Lc 18.0 | ℹ️ not gated |
163
214
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.42:1 | Lc 19.9 | ℹ️ not gated |
164
215
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 12.86:1 | Lc 75.6 | ✅ pass |
165
216
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 12.86:1 | Lc 75.6 | ✅ pass |
166
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.48:1 | Lc 74.1 | ✅ pass |
217
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.47:1 | Lc 74.1 | ✅ pass |
167
218
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 10.44:1 | Lc 73.1 | ✅ pass |
168
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.48:1 | Lc 74.1 | ✅ pass |
219
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.47:1 | Lc 74.1 | ✅ pass |
220
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
221
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
222
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
223
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
224
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.29:1 | Lc 14.6 | ℹ️ not gated |
169
225
 
170
226
  ### Phosphor Green — light
171
227
 
172
228
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
173
229
  | --- | --- | --- | --- | --- | --- | --- |
174
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.22:1 | Lc 76.6 | ✅ pass |
230
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 18.02:1 | Lc 99.5 | ✅ pass |
231
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 19.72:1 | Lc 105.8 | ✅ pass |
232
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 16.67:1 | Lc 94.4 | ✅ pass |
233
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.10:1 | Lc 91.5 | ✅ pass |
234
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 12.14:1 | Lc 97.7 | ✅ pass |
235
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 10.26:1 | Lc 86.4 | ✅ pass |
236
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.06:1 | Lc 71.4 | ✅ pass |
237
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.53:1 | Lc 77.7 | ✅ pass |
238
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.68:1 | Lc 66.3 | ✅ pass |
239
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.26:1 | Lc 77.1 | ✅ pass |
175
240
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.85:1 | Lc 83.3 | ✅ pass |
176
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.16:1 | Lc 78.9 | ✅ pass |
241
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 9.05:1 | Lc 78.5 | ✅ pass |
177
242
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 5.98:1 | Lc 74.1 | ℹ️ not gated |
178
243
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 6.32:1 | Lc 77.7 | ℹ️ not gated |
179
244
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.19:1 | Lc 80.7 | ✅ pass |
180
245
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 5.19:1 | Lc 80.7 | ✅ pass |
181
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.71:1 | Lc 68.6 | ✅ pass |
246
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 4.74:1 | Lc 69.1 | ✅ pass |
182
247
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.19:1 | Lc 75.3 | ✅ pass |
183
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.71:1 | Lc 68.6 | ✅ pass |
248
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 4.74:1 | Lc 69.1 | ✅ pass |
249
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 5.04:1 | Lc 74.6 | ✅ pass |
250
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 5.60:1 | Lc 77.8 | ✅ pass |
251
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.21:1 | Lc 78.9 | ✅ pass |
252
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 5.77:1 | Lc 78.3 | ✅ pass |
253
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.35:1 | Lc 46.4 | ℹ️ not gated |
184
254
 
185
255
  ### Phosphor Green — dark
186
256
 
187
257
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
188
258
  | --- | --- | --- | --- | --- | --- | --- |
189
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.18:1 | Lc 82.8 | ✅ pass |
259
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.11:1 | Lc 91.8 | ✅ pass |
260
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.80:1 | Lc 90.8 | ✅ pass |
261
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.57:1 | Lc 89.6 | ✅ pass |
262
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.28:1 | Lc 73.3 | ✅ pass |
263
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.30:1 | Lc 72.4 | ✅ pass |
264
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.38:1 | Lc 71.2 | ✅ pass |
265
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.22:1 | Lc 50.8 | ✅ pass |
266
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.60:1 | Lc 49.8 | ✅ pass |
267
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 6.01:1 | Lc 48.7 | ✅ pass |
268
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.13:1 | Lc 82.8 | ✅ pass |
190
269
  | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.99:1 | Lc 81.8 | ✅ pass |
191
- | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.66:1 | Lc 65.0 | ✅ pass |
270
+ | `--text-soft` | `--accent-soft` over `--surface-muted` | Neutral tag/badge text on an accent tint | AA text (4.5:1) | 6.74:1 | Lc 65.6 | ✅ pass |
192
271
  | `--accent-text` | `--accent-soft` | Accent text on an accent tint | Advisory (translucent tint — not gated) | 1.33:1 | Lc 15.3 | ℹ️ not gated |
193
272
  | `--accent-text` | `--bg-accent` | Accent text on an accent-tinted surface | Advisory (translucent tint — not gated) | 1.37:1 | Lc 17.3 | ℹ️ not gated |
194
273
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 13.75:1 | Lc 79.7 | ✅ pass |
195
274
  | `--on-accent` | `--accent` | Ink on an accent fill | AA text (4.5:1) | 13.75:1 | Lc 79.7 | ✅ pass |
196
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.27:1 | Lc 78.5 | ✅ pass |
275
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.22:1 | Lc 78.5 | ✅ pass |
197
276
  | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.16:1 | Lc 77.5 | ✅ pass |
198
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.27:1 | Lc 78.5 | ✅ pass |
277
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.22:1 | Lc 78.5 | ✅ pass |
278
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 56.6 | ✅ pass |
279
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 66.6 | ✅ pass |
280
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 41.5 | ✅ pass |
281
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 54.8 | ✅ pass |
282
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.33:1 | Lc 15.1 | ℹ️ not gated |
199
283
 
200
284
  ## Data-viz palette (advisory)
201
285
 
@@ -0,0 +1,216 @@
1
+ # Migrating 0.7 to 0.8
2
+
3
+ Machine-readable migration graph: [`MIGRATIONS.json`](../../MIGRATIONS.json) —
4
+ it has no entry for this pair, because nothing was renamed or removed. Every
5
+ class, token, attribute, and export valid in 0.7 is still valid.
6
+
7
+ 0.8.0 comes from auditing one real consumer's stylesheet in full. It repairs an
8
+ accessibility floor, adds three surfaces that consumer had to hand-write, and
9
+ changes what a colorway does. Only the last of those needs a decision from you.
10
+
11
+ ## 1. Decide what your skins should look like — the one breaking change
12
+
13
+ **Only affects you if you set `data-bronto-skin`.** If you don't, skip to §2.
14
+
15
+ Until 0.7, a colorway moved `--accent` and nothing else; the neutral canvas
16
+ stayed grey, and ADR-0001 step 4 said so. From 0.8 a colorway also re-points ten
17
+ canvas tokens per theme:
18
+
19
+ ```
20
+ --bg --bg-elevated --panel --panel-strong --panel-soft
21
+ --line --line-strong --text --text-soft --text-dim
22
+ ```
23
+
24
+ So "Amber CRT" now actually renders amber. Nothing in your source changes and no
25
+ checker will flag anything — the break is purely visual, which is why it is
26
+ worth reading rather than discovering.
27
+
28
+ **Contrast is not a regression risk.** Each neutral keeps the core token's OKLCH
29
+ *lightness* exactly and moves only hue plus a small chroma, and `check-contrast`
30
+ re-measures all 21 gated pairings per skin per theme. Status colours
31
+ (`--success` / `--warning` / `--danger` / `--info`) are untouched by design: a
32
+ warning must look like a warning in every skin.
33
+
34
+ ### If you already hand-wrote a canvas for a skin
35
+
36
+ Delete it. That workaround is what motivated this change, and a hand-rolled
37
+ version is almost certainly not contrast-gated, probably covers one theme, and
38
+ probably misses whichever skin nobody opened.
39
+
40
+ ### If you want the 0.7 look back
41
+
42
+ Re-declare the ten tokens after the skin import. They are ordinary custom
43
+ properties on a `:root[data-bronto-skin=…]` selector inside `@layer bronto`, so
44
+ un-layered app CSS wins without a specificity fight:
45
+
46
+ ```css
47
+ @import '@ponchia/ui';
48
+ @import '@ponchia/ui/css/skins.css';
49
+
50
+ /* Keep the neutral canvas grey under every colorway. */
51
+ :root[data-bronto-skin] {
52
+ --bg: #f4f4f2;
53
+ --bg-elevated: #fbfbfa;
54
+ --panel: #ffffff;
55
+ --panel-strong: #ffffff;
56
+ --panel-soft: #ececea;
57
+ --line: #d8d8d4;
58
+ --line-strong: #a8a8a2;
59
+ --text: #0a0a0a;
60
+ --text-soft: #353533;
61
+ --text-dim: #686863;
62
+ }
63
+ ```
64
+
65
+ (Those are the 0.7 light values; take the dark set from `tokens/resolved.json`
66
+ and repeat under `:root[data-theme='dark'][data-bronto-skin]`.)
67
+
68
+ ## 2. Delete your tap-target workaround
69
+
70
+ The coarse-pointer floor was written as a bare `2.9rem`, and `css/base.css` sets
71
+ `html { font-size: 0.9375rem }` — so it resolved to **43.5px**, half a pixel
72
+ under the 44 that WCAG 2.5.5 and both platform HIGs require, and less than that
73
+ under any host with a smaller root. If you noticed and declared your own 44px
74
+ floor, you can now drop it:
75
+
76
+ ```diff
77
+ -:root { --touch-target: 44px; }
78
+ -
79
+ -@media (pointer: coarse) {
80
+ - .my-control { min-block-size: var(--touch-target); }
81
+ -}
82
+ ```
83
+
84
+ Bronto's controls float to `var(--tap-target)` — `max(44px, 2.9rem)` — on their
85
+ own. For your *own* controls, consume the token rather than a literal:
86
+
87
+ ```css
88
+ @media (pointer: coarse) {
89
+ .my-control {
90
+ min-block-size: var(--tap-target); /* 44px floor, WCAG 2.5.5 */
91
+ }
92
+ }
93
+ ```
94
+
95
+ `--tap-target-min` is the WCAG 2.5.8 AA 24px floor, for controls that only have
96
+ to clear the smaller bar. Both clamp in px on purpose: **keep the clamp if you
97
+ override them.** A bare rem is how a 44px floor quietly becomes 43.5px.
98
+
99
+ ## 3. Delete your safe-area declarations
100
+
101
+ 0.7 had no `env()` awareness at all. If you declared your own insets, drop them —
102
+ the same four names now ship:
103
+
104
+ ```diff
105
+ -:root {
106
+ - --safe-area-top: env(safe-area-inset-top, 0px);
107
+ - --safe-area-bottom: env(safe-area-inset-bottom, 0px);
108
+ -}
109
+ ```
110
+
111
+ Eight viewport-anchored surfaces now read them: the app rail and topbar, a
112
+ sticky site header, the skip link, both toast stacks, the drawer modal, and the
113
+ lightbox. Every rule uses `max(<authored>, var(--safe-area-*))`, so **desktop
114
+ rendering is unchanged**.
115
+
116
+ They are indirected through custom properties rather than calling `env()` at the
117
+ point of use, which matters twice: a desktop test runner cannot emulate `env()`
118
+ but can override a property, and a host running inside its own chrome (an
119
+ embedded webview, a kiosk frame) can declare the real insets. Follow the same
120
+ convention for your own floating chrome:
121
+
122
+ ```css
123
+ .my-floating-bar {
124
+ inset-block-end: max(1rem, var(--safe-area-bottom));
125
+ }
126
+ ```
127
+
128
+ ## 4. Two new opt-in ergonomics
129
+
130
+ Neither is required; both replace a common workaround.
131
+
132
+ **`ui-button__label`** — an icon button can keep its words for the accessible
133
+ name and for text-based test selectors while giving back the pixels. One markup
134
+ shape serves both forms, and no `aria-label` can drift out of sync with the
135
+ visible wording:
136
+
137
+ ```html
138
+ <button class="ui-button ui-button--icon">
139
+ <span class="ui-icon" style="--icon-mask: …"></span>
140
+ <span class="ui-button__label">Delete</span>
141
+ </button>
142
+ ```
143
+
144
+ Drop `--icon` and the same markup renders glyph + word. The slot ellipsises
145
+ rather than wrapping, so a labelled button in a tight bar shrinks instead of
146
+ pushing its neighbours out.
147
+
148
+ **`ui-button--dense`** — for bars whose *height* is the constraint: a pane title
149
+ bar, a packed toolbar, a table row's actions. It lowers only the visual floor,
150
+ to `--tap-target-min`. The coarse-pointer block still floats it to the full
151
+ `--tap-target`, so a control you shrink for a mouse is never shrunk for a
152
+ finger. `ui.button({ size: 'dense' })` in the recipe API.
153
+
154
+ ## 5. Retire your own severity vocabulary
155
+
156
+ Nothing forces this, but it is the reason most likely to have produced
157
+ divergent code. Bronto shipped the tones without the **scale**, so consumers
158
+ invented tier names — and inside one app they drift, because each surface was
159
+ written on a different day. If you have more than one, `css/state.css` now
160
+ publishes the canonical ladder:
161
+
162
+ ```
163
+ critical › error › warning › notice › ok (+ unknown, outside the order)
164
+ ```
165
+
166
+ ```js
167
+ import { severity, SEVERITY_LEVELS } from '@ponchia/ui/classes';
168
+
169
+ severity('critical'); // { class: 'ui-severity', 'data-level': 'critical' }
170
+ severity('warning', { part: 'row' }); // { class: 'ui-severity-row', … }
171
+ SEVERITY_LEVELS; // sort and filter from this, not a local copy
172
+ ```
173
+
174
+ The level travels on `data-level` — one attribute name — so a chip, a row, a
175
+ dot, and your own element all read the same selector, and your own element can
176
+ take `var(--severity-tone)` without copying a colour table.
177
+
178
+ Map your existing tiers onto it rather than keeping both. `unknown` is the
179
+ landing spot for anything unmeasured or stale; do **not** map it to `ok`, which
180
+ is an assertion of health and is how a dead collector reads as a healthy system.
181
+
182
+ ## 6. Other surfaces you may be hand-rolling
183
+
184
+ Each of these replaced something a real consumer had built locally. None is
185
+ required.
186
+
187
+ - **`.ui-pane`** — a grab header, an in-place rename input, and an actions slot
188
+ that scrolls rather than pushing its last control past the clipped edge. If
189
+ you have a node/window/panel with a draggable title bar, this is it.
190
+ `.ui-panel` is still just a padded card.
191
+ - **`.ui-toolstrip--pane`** — a control bar belonging to one pane rather than to
192
+ the app: no frame of its own, and it refuses to wrap so a second row cannot
193
+ resize live content underneath. Mark the shrinking element with
194
+ `.ui-toolstrip__fill`.
195
+ - **`.ui-selectionbar--anchored`** (and the same on `.ui-toolstrip`) — viewport
196
+ anchoring for a floating bar, safe-area aware. Use
197
+ `--anchor-block-start` for the bar that must *not* sit under the thumb.
198
+ - **`.ui-empty-state__glyph/__lead/__hint`** and **`--invite`** — the three
199
+ parts every empty surface re-invents, plus the distinction between reporting
200
+ absence and offering the next action.
201
+
202
+ ## 7. If you read `tokens.dtcg.json`
203
+
204
+ Six new scale tokens are **deliberately absent** from it, listed in the root
205
+ extension's `omittedCssVariables`: the two tap-target floors are `max()`
206
+ comparisons and the four safe-area insets are `env()` reads, and neither has a
207
+ conforming DTCG shape. Emitting one arm of a clamp, or the 0px fallback of an
208
+ `env()`, would publish a value that is wrong everywhere it matters. Read
209
+ `tokens.json` for the authored CSS. This is the same treatment `--shadow` and
210
+ the em trackings already get.
211
+
212
+ ## Nothing else changed
213
+
214
+ No class was renamed or removed. No export moved. `bronto-ui-check` will not
215
+ report anything new for a 0.7-clean consumer — which is worth stating plainly,
216
+ because the one breaking change in this release is invisible to it.
@@ -299,6 +299,7 @@ always includes `package.json`, `README.md`, `LICENSE`, and
299
299
  | `docs/migrations/0.4-to-0.5.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
300
300
  | `docs/migrations/0.5-to-0.6.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
301
301
  | `docs/migrations/0.6-to-0.7.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
302
+ | `docs/migrations/0.7-to-0.8.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
302
303
  | `docs/adr/0001-color-system.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
303
304
  | `docs/adr/0002-scope-and-2026-baseline.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
304
305
  | `docs/adr/0003-theme-model.md` | Shipped documentation | Curated Markdown reading asset shipped in the npm tarball. |
package/docs/reference.md CHANGED
@@ -9,7 +9,7 @@ rendering of every class is the kitchen-sink demo:
9
9
  **<https://ponchia.github.io/bronto-ui/>**. Theming knobs and the token
10
10
  contract: [docs/theming.md](theming.md).
11
11
 
12
- - 644 classes across 179 component groups
12
+ - 669 classes across 183 component groups
13
13
  - Import the typed registry: `import { cls, ui, cx } from '@ponchia/ui/classes'`
14
14
  - Validate markup as data (no JS/TS): `@ponchia/ui/classes.json` — the same
15
15
  vocabulary as language-neutral JSON (`groups`, `classes`, `states`,
@@ -268,7 +268,9 @@ each one matches a real selector in the stylesheet.
268
268
  | Registry key | Class | Kind |
269
269
  | --- | --- | --- |
270
270
  | `cls.button` | `ui-button` | base |
271
+ | `cls.buttonLabel` | `ui-button__label` | part |
271
272
  | `cls.buttonDanger` | `ui-button--danger` | modifier |
273
+ | `cls.buttonDense` | `ui-button--dense` | modifier |
272
274
  | `cls.buttonGhost` | `ui-button--ghost` | modifier |
273
275
  | `cls.buttonIcon` | `ui-button--icon` | modifier |
274
276
  | `cls.buttonLg` | `ui-button--lg` | modifier |
@@ -577,6 +579,10 @@ each one matches a real selector in the stylesheet.
577
579
  | Registry key | Class | Kind |
578
580
  | --- | --- | --- |
579
581
  | `cls.emptyState` | `ui-empty-state` | base |
582
+ | `cls.emptyStateGlyph` | `ui-empty-state__glyph` | part |
583
+ | `cls.emptyStateHint` | `ui-empty-state__hint` | part |
584
+ | `cls.emptyStateLead` | `ui-empty-state__lead` | part |
585
+ | `cls.emptyStateInvite` | `ui-empty-state--invite` | modifier |
580
586
 
581
587
  ### `.ui-error-summary`
582
588
 
@@ -952,6 +958,17 @@ each one matches a real selector in the stylesheet.
952
958
  | `cls.pagination` | `ui-pagination` | base |
953
959
  | `cls.paginationItem` | `ui-pagination__item` | part |
954
960
 
961
+ ### `.ui-pane`
962
+
963
+ | Registry key | Class | Kind |
964
+ | --- | --- | --- |
965
+ | `cls.pane` | `ui-pane` | base |
966
+ | `cls.paneActions` | `ui-pane__actions` | part |
967
+ | `cls.paneBody` | `ui-pane__body` | part |
968
+ | `cls.paneHead` | `ui-pane__head` | part |
969
+ | `cls.paneTitle` | `ui-pane__title` | part |
970
+ | `cls.paneTitleInput` | `ui-pane__title-input` | part |
971
+
955
972
  ### `.ui-panel`
956
973
 
957
974
  | Registry key | Class | Kind |
@@ -1160,6 +1177,29 @@ each one matches a real selector in the stylesheet.
1160
1177
  | `cls.selectionbar` | `ui-selectionbar` | base |
1161
1178
  | `cls.selectionbarActions` | `ui-selectionbar__actions` | part |
1162
1179
  | `cls.selectionbarCount` | `ui-selectionbar__count` | part |
1180
+ | `cls.selectionbarAnchorBlockEnd` | `ui-selectionbar--anchor-block-end` | modifier |
1181
+ | `cls.selectionbarAnchorBlockStart` | `ui-selectionbar--anchor-block-start` | modifier |
1182
+ | `cls.selectionbarAnchored` | `ui-selectionbar--anchored` | modifier |
1183
+
1184
+ ### `.ui-severity`
1185
+
1186
+ | Registry key | Class | Kind |
1187
+ | --- | --- | --- |
1188
+ | `cls.severity` | `ui-severity` | base |
1189
+
1190
+ ### `.ui-severity-dot`
1191
+
1192
+ | Registry key | Class | Kind |
1193
+ | --- | --- | --- |
1194
+ | `cls.severityDot` | `ui-severity-dot` | base |
1195
+
1196
+ ### `.ui-severity-row`
1197
+
1198
+ | Registry key | Class | Kind |
1199
+ | --- | --- | --- |
1200
+ | `cls.severityRow` | `ui-severity-row` | base |
1201
+ | `cls.severityRowMeta` | `ui-severity-row__meta` | part |
1202
+ | `cls.severityRowTitle` | `ui-severity-row__title` | part |
1163
1203
 
1164
1204
  ### `.ui-shortcut`
1165
1205
 
@@ -1511,10 +1551,15 @@ each one matches a real selector in the stylesheet.
1511
1551
  | `cls.toolstripActions` | `ui-toolstrip__actions` | part |
1512
1552
  | `cls.toolstripBrand` | `ui-toolstrip__brand` | part |
1513
1553
  | `cls.toolstripContext` | `ui-toolstrip__context` | part |
1554
+ | `cls.toolstripFill` | `ui-toolstrip__fill` | part |
1514
1555
  | `cls.toolstripGroup` | `ui-toolstrip__group` | part |
1515
1556
  | `cls.toolstripSearch` | `ui-toolstrip__search` | part |
1557
+ | `cls.toolstripAnchorBlockEnd` | `ui-toolstrip--anchor-block-end` | modifier |
1558
+ | `cls.toolstripAnchorBlockStart` | `ui-toolstrip--anchor-block-start` | modifier |
1559
+ | `cls.toolstripAnchored` | `ui-toolstrip--anchored` | modifier |
1516
1560
  | `cls.toolstripCompact` | `ui-toolstrip--compact` | modifier |
1517
1561
  | `cls.toolstripFloating` | `ui-toolstrip--floating` | modifier |
1562
+ | `cls.toolstripPane` | `ui-toolstrip--pane` | modifier |
1518
1563
 
1519
1564
  ### `.ui-tooltip`
1520
1565
 
@@ -1672,6 +1717,12 @@ Exact mirror of the `:root` blocks in `css/tokens.css`
1672
1717
  | `--space-lg` | `1.35rem` |
1673
1718
  | `--space-xl` | `1.75rem` |
1674
1719
  | `--space-2xl` | `2.5rem` |
1720
+ | `--tap-target` | `max(44px, 2.9rem)` |
1721
+ | `--tap-target-min` | `max(24px, 1.6rem)` |
1722
+ | `--safe-area-top` | `env(safe-area-inset-top, 0px)` |
1723
+ | `--safe-area-right` | `env(safe-area-inset-right, 0px)` |
1724
+ | `--safe-area-bottom` | `env(safe-area-inset-bottom, 0px)` |
1725
+ | `--safe-area-left` | `env(safe-area-inset-left, 0px)` |
1675
1726
  | `--mono` | `'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', ui-monospace, monospace` |
1676
1727
  | `--sans` | `'Inter', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif` |
1677
1728
  | `--dot-font` | `'Doto', var(--mono)` |
package/docs/reporting.md CHANGED
@@ -54,18 +54,18 @@ No install? Link the same files from a CDN. Pin the version — pre-1.0, breakin
54
54
  changes ship in the minor (see [stability.md](./stability.md)):
55
55
 
56
56
  ```html
57
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/bronto.css" />
58
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/report-kit.css" />
57
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/bronto.css" />
58
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/css/report-kit.css" />
59
59
  ```
60
60
 
61
61
  Leaf-by-leaf CDN imports use the same `dist/css/` paths:
62
62
 
63
63
  ```html
64
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/bronto.css" />
65
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/report.css" />
66
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/dataviz.css" />
67
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/annotations.css" />
68
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/dist/css/legend.css" />
64
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/bronto.css" />
65
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/css/report.css" />
66
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/css/dataviz.css" />
67
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/css/annotations.css" />
68
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/dist/css/legend.css" />
69
69
  ```
70
70
 
71
71
  The CDN serves the package's own `fonts/` next to the CSS, so font URLs resolve
@@ -879,7 +879,7 @@ or validation runtime.
879
879
 
880
880
  ```json
881
881
  {
882
- "$schema": "https://cdn.jsdelivr.net/npm/@ponchia/ui@0.7.0/schemas/report-claims.v1.schema.json",
882
+ "$schema": "https://cdn.jsdelivr.net/npm/@ponchia/ui@0.8.1/schemas/report-claims.v1.schema.json",
883
883
  "schemaVersion": "bronto-report-claims.v1",
884
884
  "report": { "title": "Decision readiness", "type": "decision" },
885
885
  "claims": [