@ponchia/ui 0.3.6 → 0.4.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 (55) hide show
  1. package/CHANGELOG.md +1063 -0
  2. package/README.md +31 -14
  3. package/behaviors/index.d.ts +3 -1
  4. package/behaviors/index.js +179 -106
  5. package/classes/index.d.ts +41 -0
  6. package/classes/index.js +42 -0
  7. package/classes/vscode.css-custom-data.json +18 -22
  8. package/css/dataviz.css +96 -0
  9. package/css/disclosure.css +29 -0
  10. package/css/dots.css +32 -2
  11. package/css/feedback.css +53 -0
  12. package/css/motion.css +88 -0
  13. package/css/overlay.css +52 -1
  14. package/css/report.css +382 -0
  15. package/css/skins.css +54 -0
  16. package/css/tokens.css +56 -32
  17. package/dist/bronto.css +1 -1
  18. package/dist/css/dataviz.css +1 -0
  19. package/dist/css/disclosure.css +1 -1
  20. package/dist/css/dots.css +1 -1
  21. package/dist/css/feedback.css +1 -1
  22. package/dist/css/motion.css +1 -1
  23. package/dist/css/overlay.css +1 -1
  24. package/dist/css/report.css +1 -0
  25. package/dist/css/skins.css +1 -0
  26. package/dist/css/tokens.css +1 -1
  27. package/docs/adr/0001-color-system.md +271 -0
  28. package/docs/adr/0002-scope-and-2026-baseline.md +104 -0
  29. package/docs/adr/0003-theme-model.md +94 -0
  30. package/docs/contrast.md +170 -51
  31. package/docs/reference.md +118 -19
  32. package/docs/reporting.md +270 -0
  33. package/docs/stability.md +37 -0
  34. package/docs/theming.md +84 -7
  35. package/docs/usage.md +67 -8
  36. package/glyphs/glyphs.d.ts +14 -1
  37. package/glyphs/glyphs.js +143 -2
  38. package/llms.txt +101 -5
  39. package/package.json +79 -5
  40. package/qwik/index.d.ts +55 -0
  41. package/qwik/index.js +129 -0
  42. package/react/index.d.ts +57 -0
  43. package/react/index.js +84 -0
  44. package/solid/index.d.ts +57 -0
  45. package/solid/index.js +85 -0
  46. package/tokens/charts.d.ts +37 -0
  47. package/tokens/charts.js +96 -0
  48. package/tokens/charts.json +61 -0
  49. package/tokens/index.d.ts +3 -3
  50. package/tokens/index.js +16 -18
  51. package/tokens/index.json +32 -36
  52. package/tokens/resolved.json +39 -41
  53. package/tokens/skins.d.ts +27 -0
  54. package/tokens/skins.js +62 -0
  55. package/tokens/tokens.dtcg.json +22 -34
@@ -0,0 +1,270 @@
1
+ # Static reports
2
+
3
+ `@ponchia/ui` can dress static, LLM-authored HTML reports without a component
4
+ runtime. Load the normal bundle, then opt in to the report layer and the chart
5
+ palette only when the report needs them.
6
+
7
+ In a bundled app, package specifiers are fine because Vite or another bundler
8
+ rewrites them:
9
+
10
+ ```css
11
+ @import '@ponchia/ui';
12
+ @import '@ponchia/ui/css/report.css';
13
+ @import '@ponchia/ui/css/dataviz.css';
14
+ ```
15
+
16
+ For standalone browser HTML, use real stylesheet URLs. Package specifiers like
17
+ `@ponchia/ui/css/report.css` do not resolve in a saved `.html` file:
18
+
19
+ ```html
20
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/bronto.css" />
21
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/report.css" />
22
+ <link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/dataviz.css" />
23
+ ```
24
+
25
+ If you copy the built CSS next to the report, keep the same relationship between
26
+ `dist/bronto.css`, `dist/css/report.css`, `dist/css/dataviz.css`, and `fonts/`
27
+ so font URLs continue to resolve.
28
+
29
+ The report layer is static and PDF-first. It does not initialize behaviors and
30
+ does not sanitize content. If a report includes arbitrary LLM, CMS, or user HTML,
31
+ sanitize that content before rendering it and do not initialize
32
+ `data-bronto-*` behaviors on the generated region.
33
+
34
+ ## Canonical skeleton
35
+
36
+ ```html
37
+ <main class="ui-report ui-report--numbered">
38
+ <header class="ui-report__cover">
39
+ <p class="ui-eyebrow">Quarterly review</p>
40
+ <h1 class="ui-report__title">Personal systems report</h1>
41
+ <p class="ui-report__subtitle">A static report generated from trusted data.</p>
42
+ <ul class="ui-report__meta">
43
+ <li><time datetime="2026-06-01">2026-06-01</time></li>
44
+ <li>Static HTML</li>
45
+ <li>Chromium PDF-ready</li>
46
+ </ul>
47
+ </header>
48
+
49
+ <nav class="ui-report__toc" aria-label="Report contents">
50
+ <p class="ui-eyebrow ui-eyebrow--muted">Contents</p>
51
+ <ol>
52
+ <li><a href="#summary">Executive summary</a></li>
53
+ <li><a href="#findings">Findings</a></li>
54
+ </ol>
55
+ </nav>
56
+
57
+ <section id="summary" class="ui-report__section">
58
+ <h2 class="ui-report__section-head">Executive summary</h2>
59
+ <div class="ui-report__summary">
60
+ <p>The summary block is for the decision-level conclusion.</p>
61
+ </div>
62
+ <div class="ui-statgrid">
63
+ <div class="ui-stat">
64
+ <span class="ui-stat__label">Open risks</span>
65
+ <span class="ui-stat__value">3</span>
66
+ <span class="ui-stat__delta is-neg">+1 this week</span>
67
+ </div>
68
+ </div>
69
+ </section>
70
+
71
+ <section id="findings" class="ui-report__section">
72
+ <h2 class="ui-report__section-head">Findings</h2>
73
+ <article class="ui-report__finding" aria-labelledby="finding-1">
74
+ <p class="ui-eyebrow" id="finding-1">Finding 1 — short title</p>
75
+ <div class="ui-prose">
76
+ <p>Use prose only for narrative body content.</p>
77
+ </div>
78
+ </article>
79
+ </section>
80
+ </main>
81
+ ```
82
+
83
+ ## Composition rules
84
+
85
+ - Use `ui-report` as the page-level wrapper. Add `ui-report--numbered` when
86
+ section headings should auto-number. Add `ui-report--compact` to tighten the
87
+ whole report's vertical rhythm (gap + page padding) for dense briefs — this
88
+ is the document-level density toggle, distinct from `ui-report__cover--compact`
89
+ below, which only shrinks the cover.
90
+ - Use `ui-report__cover` for title, subtitle, author/date, and generation
91
+ metadata. Add `ui-report__cover--compact` for short screen-first reports.
92
+ Use `ui-report__header` for a compact in-page header instead of a full cover
93
+ (same role, no tall hero block). Author `ui-report__meta` as a `<ul>` — the
94
+ facts it lists are unordered.
95
+ - Use `ui-report__section` and `ui-report__section-head` for report chapters.
96
+ Keep one `h1` for the report title; use ordered `h2`/`h3` headings after it.
97
+ - Add `ui-report__section--unnumbered` to appendices, sources, or footnotes
98
+ when the report uses `ui-report--numbered` but the section should not count as
99
+ a numbered chapter.
100
+ - Use `ui-prose` only around narrative HTML you do not fully control, such as
101
+ Markdown output. Do not wrap structured app/report UI in `ui-prose`.
102
+ - Use existing primitives for shared meanings: `ui-statgrid` for KPIs,
103
+ `ui-alert` for persistent notices, `ui-table` for structured evidence,
104
+ `ui-timeline` for events, `ui-meter` for measured values, and `ui-num` for
105
+ non-table numeric values.
106
+ - Give `ui-report__finding` blocks an accessible name so the visual grouping is
107
+ also programmatic: point `aria-labelledby` at the block's `ui-eyebrow` label
108
+ (give it an `id`), or lead with a real heading. Do the same for a
109
+ multi-part `ui-report__evidence` block; a single evidence table is already
110
+ named by its own `<caption>`.
111
+ - Name the `ui-report__sources` and `ui-report__footnotes` blocks
112
+ (`aria-labelledby` on their `ui-eyebrow`), and link footnotes both ways:
113
+ an in-text `<sup><a id="fnref1" href="#fn1">1</a></sup>` to an
114
+ `<ol><li id="fn1">… <a href="#fnref1">↩</a></li></ol>`. They are real
115
+ regions, not decoration.
116
+ - Every `<table>` that carries report data should have a `<caption>`, header
117
+ cells, and `.is-num` on numeric columns. (`.is-num`, `.is-pos`, `.is-neg`
118
+ and `.is-key` only take effect inside `.ui-table` cells and `.ui-stat`
119
+ deltas — they are not free-standing utilities.) Keep raw Markdown tables
120
+ inside `ui-prose`; use `.ui-table` for curated evidence tables. If a
121
+ `ui-report__evidence` block contains only a `ui-table-wrap`, the report layer
122
+ removes the inner frame so evidence tables do not look double-boxed.
123
+ - Every `<figure>` should include a `figcaption` using
124
+ `ui-chart__caption` (chart figures) or `ui-report__caption` (any other
125
+ report figure); the two are interchangeable in style.
126
+ - Do not use raw color values. Theme with `--accent`; use status tones for
127
+ status; use chart tokens only in chart figures.
128
+
129
+ ## Chart figure recipe
130
+
131
+ The report layer supplies chart containers, legends, and a small static bar
132
+ pattern, not a chart engine. For CSS/HTML/SVG charts, pair each chart color with
133
+ a direct label, a pattern, or a fallback table.
134
+
135
+ ```html
136
+ <figure class="ui-report__figure ui-chart ui-print-exact" role="group" aria-labelledby="chart-title">
137
+ <figcaption id="chart-title" class="ui-chart__caption">
138
+ Fig 1 - Weekly focus split
139
+ </figcaption>
140
+ <ul class="ui-chart__legend" aria-label="Series">
141
+ <li>
142
+ <span
143
+ class="ui-chart__swatch"
144
+ style="--chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
145
+ aria-hidden="true"
146
+ ></span>
147
+ Research
148
+ </li>
149
+ <li>
150
+ <span
151
+ class="ui-chart__swatch"
152
+ style="--chart-color: var(--chart-2); --chart-pattern: var(--chart-pattern-2)"
153
+ aria-hidden="true"
154
+ ></span>
155
+ Delivery
156
+ </li>
157
+ </ul>
158
+ <div class="ui-chart__plot" aria-hidden="true">
159
+ <div
160
+ class="ui-chart__bar"
161
+ style="--chart-value: 72%; --chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
162
+ >
163
+ <div class="ui-chart__label"><span>Research</span><span>18 h</span></div>
164
+ <div class="ui-chart__track"><div class="ui-chart__fill"></div></div>
165
+ </div>
166
+ <div
167
+ class="ui-chart__bar"
168
+ style="--chart-value: 44%; --chart-color: var(--chart-2); --chart-pattern: var(--chart-pattern-2)"
169
+ >
170
+ <div class="ui-chart__label"><span>Delivery</span><span>11 h</span></div>
171
+ <div class="ui-chart__track"><div class="ui-chart__fill"></div></div>
172
+ </div>
173
+ </div>
174
+ <div class="ui-chart__fallback">
175
+ <div class="ui-table-wrap">
176
+ <table class="ui-table ui-table--dense">
177
+ <caption>Chart source data</caption>
178
+ <thead>
179
+ <tr><th>Series</th><th class="is-num">Hours</th></tr>
180
+ </thead>
181
+ <tbody>
182
+ <tr><td>Research</td><td class="is-num">18</td></tr>
183
+ <tr><td>Delivery</td><td class="is-num">11</td></tr>
184
+ </tbody>
185
+ </table>
186
+ </div>
187
+ </div>
188
+ </figure>
189
+ ```
190
+
191
+ For canvas or SVG libraries, import resolved series colors from
192
+ `@ponchia/ui/charts.json` and keep the same legend/caption/fallback structure in
193
+ the surrounding HTML.
194
+
195
+ ## Timeline recipe
196
+
197
+ Incident reviews and research logs should use the existing timeline primitive
198
+ with its required item and time parts. Do not emit a bare list and expect it to
199
+ style itself.
200
+
201
+ ```html
202
+ <ol class="ui-timeline">
203
+ <li class="ui-timeline__item">
204
+ <time class="ui-timeline__time" datetime="2026-06-01T09:12:00">09:12</time>
205
+ <span>Latency alert fired for p95 over threshold.</span>
206
+ </li>
207
+ <li class="ui-timeline__item" aria-current="step">
208
+ <time class="ui-timeline__time" datetime="2026-06-01T09:54:00">09:54</time>
209
+ <span>Latency returned to baseline.</span>
210
+ </li>
211
+ </ol>
212
+ ```
213
+
214
+ ## Common templates
215
+
216
+ - Executive brief: compact cover, one summary block, KPI `ui-statgrid`, short
217
+ findings, and sources.
218
+ - Research brief: compact header, decision frame, evidence table, quotes or
219
+ prose excerpts, and sources.
220
+ - Incident review: compact cover, summary, `ui-timeline`, corrective-action
221
+ evidence table, and footnotes.
222
+ - Project status: compact header, KPI `ui-statgrid`, chart figure with fallback
223
+ data, print-only notes, and unnumbered appendix.
224
+
225
+ ## Print and PDF
226
+
227
+ The supported export target is modern Chromium print/PDF. Two ways to produce
228
+ the file:
229
+
230
+ - **By hand:** open the report in Chrome/Edge → Print (Cmd/Ctrl+P) → "Save as
231
+ PDF". In **More settings**, enable **Background graphics** (the dialog's
232
+ equivalent of `printBackground` — without it chart fills and swatches drop
233
+ out), and pick the paper size there. Paper size is a browser print setting,
234
+ not a token; the layer only themes the page _margin_ via
235
+ `--report-page-margin`.
236
+ - **Headless (agents/CI):** `await page.pdf({ format: 'A4', printBackground: true })`
237
+ with Playwright or Puppeteer.
238
+
239
+ The report prints ink-on-white regardless of the on-screen theme. Older
240
+ HTML-to-PDF engines are not part of the browser floor and may not support
241
+ cascade layers, `oklch()`, `color-mix()`, `:has()`, or modern paged-media
242
+ behavior.
243
+
244
+ - Use `ui-print-only` for content that should appear only in print.
245
+ - Use `ui-screen-only` for navigation or helper content that should not print.
246
+ - Use `ui-break-before` and `ui-break-after` for deliberate page boundaries.
247
+ - Use `ui-keep` on short figures, findings, and summary blocks that should not
248
+ split across pages.
249
+ - Use `ui-print-exact` on charts whose fills and patterns must survive PDF
250
+ export.
251
+ - External links inside `ui-prose` print their URL automatically through the
252
+ base print stylesheet.
253
+
254
+ **Not provided** (use the browser's paged-media features, or author the markup
255
+ yourself): running headers/footers with "Page X of Y", automatic table-of-contents
256
+ pagination, multi-column layout, and automatic citation/footnote numbering. The
257
+ report layer is a document grammar, not a paged-media or citation engine — do not
258
+ fake page numbers with inert markup.
259
+
260
+ ## LLM checklist
261
+
262
+ Before returning a report, an LLM should verify:
263
+
264
+ - All `ui-*` classes exist in `@ponchia/ui/classes`.
265
+ - The document has one `h1`, ordered headings, and a single main report region.
266
+ - Tables have captions and header cells.
267
+ - Charts have captions, direct labels or legends, and fallback data.
268
+ - No raw chromatic colors appear in inline styles.
269
+ - No remote scripts, styles, iframes, or images are required for the report.
270
+ - Generated/untrusted body HTML was sanitized before insertion.
@@ -0,0 +1,37 @@
1
+ # Public API Stability
2
+
3
+ `@ponchia/ui` is pre-1.0. Breaking changes ship in the minor (`0.x.0`), and
4
+ patches are non-breaking. This matrix defines what counts as public API.
5
+
6
+ | Surface | Stability | Contract |
7
+ | --- | --- | --- |
8
+ | CSS package root (`@ponchia/ui`) | Stable | CSS-only entrypoint. CSS side-effect imports are supported in CSS-aware bundlers; Node/runtime JS root imports are not. |
9
+ | JS module format | Stable | JS subpaths are ESM-only. CommonJS consumers use dynamic `import()`. |
10
+ | CSS class names (`.ui-*`) | Stable | Names and documented modifier semantics are public. Internal selector structure and leaf-file boundaries may change. |
11
+ | Class recipes (`@ponchia/ui/classes`) | Stable | Exported `cls`, `ui`, `cx`, recipe names, and option unions are public. |
12
+ | Design tokens | Stable names/roles | Token names and documented roles are public. Exact values and generated colour math outputs may change for visual tuning before 1.0. |
13
+ | `--accent-1..6` | Stable names/roles | A subtle-to-bold accent ramp derived from `--accent`. Exact resolved values are visual tuning; algorithm changes require release-note visibility and resolver/browser checks. |
14
+ | Behavior attributes (`data-bronto-*`) | Stable | Attribute names and documented markup relationships are public. Behavior internals are not. |
15
+ | Behavior functions (`@ponchia/ui/behaviors`) | Stable | Exported function names, option names, custom events, SSR no-op behavior, idempotency, and cleanup-returning contract are public. |
16
+ | Glyph registry/renderers (`@ponchia/ui/glyphs`) | Stable additive | Existing glyph names stay valid. New glyphs are additive. Renderer option names and accessibility defaults are public. |
17
+ | `.ui-icon` mask renderer | Stable | Class name, `--icon-size`, currentColor inheritance, and `--icon-mask` contract are public. The internal data URL encoding is not. |
18
+ | React/Solid/Qwik bindings | Stable thin adapters | Hook/primitive names, optional peer behavior, root ref/signal/resolver support, and cleanup lifecycle are public. They remain wrappers over vanilla behaviors, not component APIs. |
19
+ | Skins (`@ponchia/ui/skins`, `css/skins.css`) | Stable additive | Existing skin names stay valid. New skins are additive. Skins are root-level choices. |
20
+ | Charts (`@ponchia/ui/charts`, `charts.json`, `css/dataviz.css`) | Stable additive | Token names, JSON shape, and 8 categorical slots are public. Exact palette values may tune if gates and release notes justify it. |
21
+ | Reports (`css/report.css`, `.ui-report*`, `.ui-chart*`, print utilities) | Stable additive | Report class names, BEM part names, chart helper class names, and print utility names are public. Report CSS is opt-in and not imported by the default bundle. |
22
+ | Generated docs shipped in npm | Stable paths | `llms.txt` and exported docs paths stay shipped and resolvable within a compatible minor. Markdown/text assets are for reading unless your runtime has a loader. Generated content may change with the source contract. |
23
+ | Demo, examples, tests, scripts | Internal | Useful for learning and verification, but not shipped runtime API unless a path is explicitly exported in `package.json`. |
24
+
25
+ ## Trust Boundary
26
+
27
+ Behaviors assume trusted application markup. If a delegated root includes
28
+ untrusted CMS or user HTML, sanitize it first or do not initialize behaviors on
29
+ that root. The behavior layer intentionally lets authored `data-bronto-*`,
30
+ `aria-controls`, and `id` relationships control elements inside the root.
31
+ Dialog, disclosure, and popover targets resolve root-first and then
32
+ document-wide for body/portal-mounted overlays; scoped roots are not a sandbox
33
+ for untrusted markup.
34
+
35
+ The one-node glyph mask renderer emits inline style/custom-property data. Apps
36
+ with a strict CSP should either allow the required `data:` mask/image source and
37
+ inline custom-property style path, or use the DOM dot/solid renderers.
package/docs/theming.md CHANGED
@@ -4,13 +4,15 @@
4
4
  This is the **stable, supported surface** for re-branding without forking.
5
5
  Anything not listed here is internal and may change between minor versions.
6
6
 
7
- ## The one knob: `--accent`
7
+ ## The brand knob: `--accent`
8
8
 
9
- The whole accent family derives from `--accent` via `color-mix()`:
9
+ The accent family derives from `--accent` via `color-mix()` and the
10
+ theme-owned neutral ramp endpoint:
10
11
 
11
12
  | Token | Derivation (light / dark) | Role |
12
13
  | --------------------- | -------------------------------------------- | ---- |
13
14
  | `--accent-strong` | `--accent` mixed 83% with black / 84% white | darker/lighter accent for hover, emphasis |
15
+ | `--accent-ramp-end` | white / black | neutral endpoint for the low-chroma OKLCH ramp |
14
16
  | `--accent-text` | `var(--accent-strong)` (alias) | **accent used as foreground text** — the on-surface, AA-safe one |
15
17
  | `--accent-soft` | `--accent` at 10% / 14% over transparent | tinted fills |
16
18
  | `--bg-accent` | `--accent` at 6% / 8% | faint accent backgrounds |
@@ -49,6 +51,14 @@ fills — follows automatically, in both light and dark.
49
51
  - **Spacing** — override the `--space-2xs … --space-2xl` scale, or use a
50
52
  preset: `data-density="compact"` / `data-density="comfortable"` on any
51
53
  element (defaults to the middle scale).
54
+ - **Dark surface** — the dark theme's base is a deliberately *elevated*
55
+ near-black (`--bg: #121212`) for readability: pure black + bright text
56
+ causes halation, and near-black-on-black surface steps are imperceptible.
57
+ For OLED power-saving or the original true-black "Nothing" look, opt in with
58
+ `data-surface="oled"` on `:root` (a root-level attribute like `data-theme`).
59
+ It only affects the dark theme and is a CSS-only preset (not in the JS token
60
+ model), so it never blacks out the light theme. See
61
+ [ADR-0003](adr/0003-theme-model.md) for the theme-model rationale.
52
62
  - **Radius** — `--radius-sm … --radius-xl`, `--radius-pill`. The Nothing
53
63
  default is near-sharp; raise these for a softer brand.
54
64
  - **Type** — `--display` (dot-matrix face), `--mono`, `--sans`. Override
@@ -115,7 +125,7 @@ is not. The values above are *measured* (white label on `#a8431a` =
115
125
  the same check. Don't set one global `--accent` and hope — that is why
116
126
  this example is split per theme.
117
127
 
118
- ## Token tiers (0.3.1)
128
+ ## Token tiers
119
129
 
120
130
  Three additive, non-breaking tiers sit on top of the primitives. The
121
131
  short legacy names (`--panel`, `--line`, `--accent`, …) keep working
@@ -131,9 +141,11 @@ coarser-grained handles.
131
141
  primitives, so light/dark still Just Works.
132
142
  - **Accent ramp — `--accent-1 … --accent-6`.** A stepped family
133
143
  (subtle → bold) derived from the single `--accent` knob via
134
- `color-mix` against the theme background. Re-brands and theme-adapts
135
- automatically. This is the palette for charts / data-viz / multi-state
136
- surfaces (the use case the JS token export advertises).
144
+ `color-mix(in oklch, …)` against a per-theme white/black endpoint
145
+ (`--accent-ramp-end`). Re-brands and theme-adapts automatically. Steps
146
+ 1–4 are subtle surfaces; steps 5–6 are the accent and strong accent.
147
+ Exact hex outputs are visual tuning, but the token names and roles are
148
+ stable.
137
149
  - **Neutral ramp — `--surface-1 … --surface-6`** (low → high contrast
138
150
  against `--bg`) for layered surfaces without hand-picking greys.
139
151
  - **Stacking scale — `--z-base / -raised / -sticky / -overlay /
@@ -141,7 +153,72 @@ coarser-grained handles.
141
153
  these; override one to slot your app's own layers around the
142
154
  framework's without specificity/`z-index` wars.
143
155
 
144
- All four tiers are in the DTCG export and the JS token model.
156
+ All four tiers are in the DTCG export and the JS token model. The full
157
+ five-tier colour model and its rules live in
158
+ [ADR-0001](adr/0001-color-system.md).
159
+
160
+ ## Display colorways (`data-bronto-skin`)
161
+
162
+ Opt-in single-hue colorways, shipped as a **separate** entrypoint (never in
163
+ the default bundle):
164
+
165
+ ```html
166
+ <link rel="stylesheet" href="@ponchia/ui/css/skins.css" />
167
+ <html data-theme="dark" data-bronto-skin="phosphor-green">
168
+
169
+ </html>
170
+ ```
171
+
172
+ `data-bronto-skin="amber-crt | phosphor-green | e-ink"` is a **root-level**
173
+ choice, like `data-theme` — put it on `:root`/`<html>`. It re-points the one
174
+ `--accent` (per theme, authored in OKLCH); the derived family, focus ring,
175
+ dot-matrix and glyphs follow automatically, and status colours + the neutral
176
+ canvas are untouched. A colorway is _not_ a second accent — it swaps the one
177
+ you have, so the one-accent discipline holds.
178
+
179
+ - **Root-level only.** The accent's derived family (`--accent-strong`/`-text`,
180
+ `--field-dot-accent`, `--accent-1..6`, …) is `color-mix(… var(--accent) …)`
181
+ declared on `:root`; it only re-evaluates on the element that carries it. A
182
+ skin on a subtree would leave that family stale, so the selectors are
183
+ `:root`-anchored and a subtree skin simply no-ops.
184
+ - **Phosphor bloom.** The `amber-crt` / `phosphor-green` skins set
185
+ `--dotmatrix-glow` (a Tier-3 display knob, default `0`) in dark, so the
186
+ dot-matrix gains a CRT-style glow. Set it yourself on any `.ui-dotmatrix`
187
+ to tune the bloom.
188
+ - **Contrast-gated.** Every shipped skin accent meets the same WCAG AA / 3:1
189
+ floors as the core palette — see [contrast.md](contrast.md) → "Display
190
+ colorways". (Your _own_ `--accent` re-brand is still your obligation; the
191
+ guarantee covers the shipped palettes and skins.)
192
+
193
+ ## Data-viz palette (`@ponchia/ui/css/dataviz.css`)
194
+
195
+ Opt-in Tier-4 chart colours for dashboards — **charts only, never UI chrome**
196
+ (a build gate fails on `var(--chart-*)` in component CSS), and never in the
197
+ default bundle.
198
+
199
+ ```html
200
+ <link rel="stylesheet" href="@ponchia/ui/css/dataviz.css" />
201
+ ```
202
+
203
+ ```js
204
+ // resolved hex for canvas / SVG / Chart.js etc.
205
+ import charts from '@ponchia/ui/charts.json' with { type: 'json' };
206
+ const series = charts.dark.categorical; // ['#ff3b41', '#e69f00', …] — series 0 = accent
207
+ ```
208
+
209
+ - **Categorical `--chart-1..8`** — hybrid accent-led: series 1 is the live
210
+ `var(--accent)` (your brand leads), series 2–8 are the Okabe-Ito
211
+ colourblind-safe set. The set is **gated for mutual distinguishability under
212
+ normal + simulated protanopia/deuteranopia/tritanopia** (OKLab ΔE).
213
+ - **Sequential `--chart-seq-1..6`** — single-hue light→dark, for
214
+ heatmaps/intensity. **Diverging `--chart-div-1..7`** — blue↔neutral↔orange,
215
+ for ±/gains-losses.
216
+ - **Pattern fills `--chart-pattern-1..8`** — a dot-matrix second channel so
217
+ colour is never the sole signal (WCAG 1.4.1). Pair colour N with pattern N:
218
+ `background: var(--chart-2); background-image: var(--chart-pattern-2); background-size: var(--chart-pattern-size); --chart-pattern-ink: rgb(0 0 0 / .34);`
219
+ - A chart colour's WCAG ratio vs the background is published **advisory** in
220
+ [contrast.md](contrast.md) (a fill is not body text) — pick a darker series
221
+ for thin lines/points, or lean on the pattern.
145
222
 
146
223
  ## Accessibility markup contracts
147
224
 
package/docs/usage.md CHANGED
@@ -60,6 +60,22 @@ Rule of thumb: state → dot, classification → badge, user-controlled value
60
60
  `.ui-card` itself, so card padding/border stays the card's and prose
61
61
  rhythm stays the content's. One responsibility per element.
62
62
 
63
+ ## Static reports
64
+
65
+ Use the opt-in `@ponchia/ui/css/report.css` layer for static, PDF-first
66
+ reports. A report composes `ui-report` + existing primitives: `ui-statgrid`
67
+ for KPIs, `ui-alert` for persistent notices, `ui-table` for evidence,
68
+ `ui-timeline` for events, `ui-meter` for measured values, and `ui-prose` only
69
+ for narrative body content you do not fully control.
70
+
71
+ Do not turn every report block into a card. Use `ui-report__summary`,
72
+ `ui-report__finding`, and `ui-report__evidence` for document structure; use
73
+ `ui-card` only when the block is genuinely a repeated card item. Charts use
74
+ the report `ui-chart*` wrappers plus the opt-in data-viz tokens; simple static
75
+ bar charts can use `ui-chart__plot`, `ui-chart__bar`, and `ui-chart__fill`.
76
+ Always include a caption and fallback data. Full LLM/static report cookbook:
77
+ [reporting.md](reporting.md).
78
+
63
79
  ## Buttons: variant and size
64
80
 
65
81
  - **primary** — the single most important action in a view. Aim for one.
@@ -148,12 +164,14 @@ infinite-clone track). It's a gallery: the user drives it.
148
164
 
149
165
  ## Display glyphs: when (and when not)
150
166
 
151
- `@ponchia/ui/glyphs` is a 43-glyph dot-matrix icon set — navigation
167
+ `@ponchia/ui/glyphs` is a 48-glyph dot-matrix icon set — navigation
152
168
  (`arrow-*`, `chevron-*`), actions (`check`, `close`, `plus`, `minus`,
153
169
  `search`, `menu`, `gear`), status (`info`, `warning`, `bell`, `lock`) and
154
- common marks (`home`, `user`, `heart`, `star`, `spark`) — rendered on the
170
+ common marks (`home`, `user`, `heart`, `star`, `spark`, circle-family marks) — rendered on the
155
171
  `.ui-dotmatrix` primitive, so they re-skin with the same `--field-dot*`
156
- tokens as every other dot surface (no SVG, no icon font).
172
+ tokens as every other dot surface. The default and `solid` renderers emit
173
+ dot-matrix DOM, not an icon font; the dense `.ui-icon` renderer uses an
174
+ internal SVG data URL as a CSS mask so it can stay one DOM node.
157
175
 
158
176
  **Two rendering modes — pick by size.** The dots need physical room to
159
177
  read, so the default _dot_ look is for **display** sizes (~40px up: hero
@@ -198,11 +216,52 @@ A few sharp edges to know:
198
216
  - **Directional glyphs are physical, not logical.** `arrow-left/right`,
199
217
  `chevron-left/right` are fixed bitmaps; in an RTL context flip them yourself
200
218
  (e.g. swap the name, or `transform: scaleX(-1)`), the framework won't.
201
- - **Cost.** Each glyph is a 16×16 grid = 256 cells (DOM nodes / spans),
202
- regardless of mode; `anim: 'reveal'` adds a per-cell `--i` (≈doubles the
203
- string size). Fine for display marks and the odd inline icon — but don't
204
- render hundreds at once (e.g. one in every row of a long table) without
205
- measuring.
219
+ - **Cost / icon-at-scale.** The default (cell) render is a 16×16 grid = 256
220
+ cells (DOM nodes), regardless of `solid`/`grid`; `anim: 'reveal'` adds a
221
+ per-cell `--i`. That's the dot-matrix display look — but for **many** icons
222
+ (one in every row of a long table) use **`renderGlyph(name, { render: 'mask' })`**:
223
+ it returns a **single** `.ui-icon` element masked by the bitmap (one node, not
224
+ 256), sizes to `size` / `--icon-size` (default `1em`) and inherits
225
+ `currentColor` — a normal inline icon. Pick `render: 'mask'` for icons,
226
+ the cell render for display marks/animation.
227
+
228
+ ## Colorways: when to reach for a skin
229
+
230
+ `@ponchia/ui/css/skins.css` adds `data-bronto-skin="amber-crt |
231
+ phosphor-green | e-ink"` — a **root-level** colorway (apply on `<html>`, like
232
+ `data-theme`) that re-points the one accent to a different single hue.
233
+
234
+ - **Use a skin** when you want a distinct, on-brand _look_ (a phosphor/CRT or
235
+ e-ink feel) for the whole page or app — it's the supported, contrast-gated
236
+ way to recolour without leaving the design system.
237
+ - **Use a raw `--accent` override** when you just need _your_ brand hue: it's
238
+ one declaration (see "Re-brand obligations" below) — but then contrast is
239
+ yours, whereas the shipped skins are pre-gated.
240
+ - **Don't** put `data-bronto-skin` on a subtree — it's root-level by design
241
+ (the derived accent family only recomputes at `:root`); a subtree skin
242
+ no-ops. For a one-section recolour, scope a raw `--accent` override instead.
243
+ - It's **opt-in**: a separate stylesheet, never in `dist/bronto.css`. No skin
244
+ imported → zero cost. Full detail in [theming.md](theming.md) → "Display
245
+ colorways".
246
+
247
+ ## Data-viz colours: charts, not chrome
248
+
249
+ `@ponchia/ui/css/dataviz.css` (opt-in) adds a Tier-4 chart palette for
250
+ dashboards: `--chart-1..8` (categorical), `--chart-seq-*` (sequential),
251
+ `--chart-div-*` (diverging), and `--chart-pattern-1..8` (dot-matrix fills).
252
+
253
+ - **Use it for charts only.** These are not UI tokens — a build gate fails if
254
+ `var(--chart-*)` appears in component CSS. Style buttons/badges with the
255
+ accent/status tiers, not chart colours.
256
+ - **Series 1 is the accent**, so your brand leads the palette; series 2–8 are a
257
+ colourblind-safe set (gated for distinctness under protan/deutan/tritan).
258
+ - **Always pair colour with pattern** (`--chart-pattern-N`) and/or a direct
259
+ label — never colour alone (WCAG 1.4.1):
260
+ `background: var(--chart-3); background-image: var(--chart-pattern-3); background-size: var(--chart-pattern-size);`
261
+ - **In JS** (Chart.js, canvas, SVG): import resolved hex from
262
+ `@ponchia/ui/charts.json` (`{ light, dark }`, series 1 = the resolved accent).
263
+ Cap a chart at ~8 series. Full detail in [theming.md](theming.md) →
264
+ "Data-viz palette".
206
265
 
207
266
  ## When to add a behavior
208
267
 
@@ -10,10 +10,12 @@ export type GlyphName =
10
10
  | 'arrow-up'
11
11
  | 'bell'
12
12
  | 'check'
13
+ | 'check-circle'
13
14
  | 'chevron-down'
14
15
  | 'chevron-left'
15
16
  | 'chevron-right'
16
17
  | 'chevron-up'
18
+ | 'circle'
17
19
  | 'clock'
18
20
  | 'close'
19
21
  | 'download'
@@ -32,12 +34,14 @@ export type GlyphName =
32
34
  | 'mail'
33
35
  | 'menu'
34
36
  | 'minus'
37
+ | 'minus-circle'
35
38
  | 'moon'
36
39
  | 'more-horizontal'
37
40
  | 'more-vertical'
38
41
  | 'pause'
39
42
  | 'play'
40
43
  | 'plus'
44
+ | 'plus-circle'
41
45
  | 'refresh'
42
46
  | 'search'
43
47
  | 'spark'
@@ -46,7 +50,8 @@ export type GlyphName =
46
50
  | 'trash'
47
51
  | 'upload'
48
52
  | 'user'
49
- | 'warning';
53
+ | 'warning'
54
+ | 'x-circle';
50
55
 
51
56
  /** A glyph name, or any string — for dynamic dispatch (a CMS/config value). The
52
57
  * known names still autocomplete; an unknown name hits the runtime fallback
@@ -81,6 +86,14 @@ export interface RenderGlyphOptions {
81
86
  /** CSS length for the gap between dots (sets `--dotmatrix-gap`; sanitized
82
87
  * the same way as `dot`). */
83
88
  gap?: string;
89
+ /** `'mask'` returns a single `.ui-icon` element masked by the glyph bitmap
90
+ * (one DOM node, not GLYPH_SIZE²) — the icon-at-scale path. Inherits
91
+ * `currentColor`; the cell-mode options above don't apply. Needs the
92
+ * `.ui-icon` rule from `@ponchia/ui/css`. */
93
+ render?: 'mask';
94
+ /** With `render: 'mask'`, the icon size (sets `--icon-size`; defaults to
95
+ * `1em`; sanitized to a length/calc allowlist). */
96
+ size?: string;
84
97
  }
85
98
 
86
99
  /** The grid edge length (rows = cols = 16). */