@ponchia/ui 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +552 -8
  2. package/MIGRATIONS.json +106 -0
  3. package/README.md +34 -8
  4. package/annotations/index.d.ts +402 -0
  5. package/annotations/index.d.ts.map +1 -0
  6. package/annotations/index.js +792 -0
  7. package/behaviors/carousel.js +198 -0
  8. package/behaviors/combobox.js +226 -0
  9. package/behaviors/command.js +190 -0
  10. package/behaviors/connectors.js +95 -0
  11. package/behaviors/crosshair.js +57 -0
  12. package/behaviors/dialog.js +74 -0
  13. package/behaviors/disclosure.js +26 -0
  14. package/behaviors/dismissible.js +25 -0
  15. package/behaviors/forms.js +186 -0
  16. package/behaviors/glyph.js +108 -0
  17. package/behaviors/index.d.ts +79 -0
  18. package/behaviors/index.js +18 -1409
  19. package/behaviors/internal.js +97 -0
  20. package/behaviors/legend.js +67 -0
  21. package/behaviors/menu.js +47 -0
  22. package/behaviors/popover.js +179 -0
  23. package/behaviors/spotlight.js +52 -0
  24. package/behaviors/table.js +136 -0
  25. package/behaviors/tabs.js +103 -0
  26. package/behaviors/theme.js +84 -0
  27. package/behaviors/toast.js +164 -0
  28. package/classes/classes.json +1857 -0
  29. package/classes/index.d.ts +306 -13
  30. package/classes/index.js +339 -12
  31. package/classes/vscode.css-custom-data.json +12 -0
  32. package/connectors/index.d.ts +191 -0
  33. package/connectors/index.d.ts.map +1 -0
  34. package/connectors/index.js +275 -0
  35. package/css/analytical.css +21 -0
  36. package/css/annotations.css +292 -0
  37. package/css/app.css +43 -13
  38. package/css/base.css +15 -10
  39. package/css/command.css +97 -0
  40. package/css/connectors.css +110 -0
  41. package/css/content.css +7 -1
  42. package/css/crosshair.css +100 -0
  43. package/css/dataviz.css +5 -1
  44. package/css/disclosure.css +38 -6
  45. package/css/dots.css +57 -0
  46. package/css/feedback.css +111 -2
  47. package/css/fonts.css +11 -7
  48. package/css/forms.css +42 -1
  49. package/css/generated.css +117 -0
  50. package/css/legend.css +272 -0
  51. package/css/marks.css +174 -0
  52. package/css/motion.css +24 -44
  53. package/css/navigation.css +7 -0
  54. package/css/overlay.css +31 -1
  55. package/css/primitives.css +109 -5
  56. package/css/report.css +39 -81
  57. package/css/selection.css +46 -0
  58. package/css/site.css +16 -2
  59. package/css/sources.css +221 -0
  60. package/css/spotlight.css +104 -0
  61. package/css/state.css +121 -0
  62. package/css/tokens.css +60 -37
  63. package/css/workbench.css +83 -0
  64. package/dist/bronto.css +1 -1
  65. package/dist/css/analytical.css +1 -0
  66. package/dist/css/annotations.css +1 -0
  67. package/dist/css/app.css +1 -1
  68. package/dist/css/base.css +1 -1
  69. package/dist/css/command.css +1 -0
  70. package/dist/css/connectors.css +1 -0
  71. package/dist/css/content.css +1 -1
  72. package/dist/css/crosshair.css +1 -0
  73. package/dist/css/disclosure.css +1 -1
  74. package/dist/css/dots.css +1 -1
  75. package/dist/css/feedback.css +1 -1
  76. package/dist/css/fonts.css +1 -1
  77. package/dist/css/forms.css +1 -1
  78. package/dist/css/generated.css +1 -0
  79. package/dist/css/legend.css +1 -0
  80. package/dist/css/marks.css +1 -0
  81. package/dist/css/motion.css +1 -1
  82. package/dist/css/navigation.css +1 -1
  83. package/dist/css/overlay.css +1 -1
  84. package/dist/css/primitives.css +1 -1
  85. package/dist/css/report.css +1 -1
  86. package/dist/css/selection.css +1 -0
  87. package/dist/css/site.css +1 -1
  88. package/dist/css/sources.css +1 -0
  89. package/dist/css/spotlight.css +1 -0
  90. package/dist/css/state.css +1 -0
  91. package/dist/css/tokens.css +1 -1
  92. package/dist/css/workbench.css +1 -0
  93. package/docs/adr/0003-theme-model.md +7 -4
  94. package/docs/annotations.md +425 -0
  95. package/docs/architecture.md +246 -0
  96. package/docs/command.md +95 -0
  97. package/docs/connectors.md +91 -0
  98. package/docs/contrast.md +116 -92
  99. package/docs/crosshair.md +63 -0
  100. package/docs/d2.md +195 -0
  101. package/docs/generated.md +91 -0
  102. package/docs/legends.md +184 -0
  103. package/docs/marks.md +93 -0
  104. package/docs/mermaid.md +152 -0
  105. package/docs/reference.md +385 -23
  106. package/docs/reporting.md +436 -63
  107. package/docs/selection.md +40 -0
  108. package/docs/sources.md +137 -0
  109. package/docs/spotlight.md +78 -0
  110. package/docs/stability.md +24 -2
  111. package/docs/state.md +85 -0
  112. package/docs/usage.md +123 -4
  113. package/docs/vega.md +225 -0
  114. package/docs/workbench.md +78 -0
  115. package/fonts/doto-400.woff2 +0 -0
  116. package/fonts/doto-500.woff2 +0 -0
  117. package/fonts/doto-600.woff2 +0 -0
  118. package/fonts/doto-700.woff2 +0 -0
  119. package/fonts/doto-800.woff2 +0 -0
  120. package/fonts/doto-900.woff2 +0 -0
  121. package/glyphs/glyphs.js +6 -4
  122. package/llms.txt +362 -14
  123. package/package.json +115 -12
  124. package/qwik/index.d.ts +42 -54
  125. package/qwik/index.d.ts.map +1 -0
  126. package/qwik/index.js +75 -3
  127. package/react/index.d.ts +39 -56
  128. package/react/index.d.ts.map +1 -0
  129. package/react/index.js +67 -3
  130. package/solid/index.d.ts +64 -56
  131. package/solid/index.d.ts.map +1 -0
  132. package/solid/index.js +70 -3
  133. package/tokens/d2.d.ts +38 -0
  134. package/tokens/d2.js +71 -0
  135. package/tokens/d2.json +43 -0
  136. package/tokens/index.d.ts +5 -5
  137. package/tokens/index.js +23 -5
  138. package/tokens/index.json +9 -0
  139. package/tokens/mermaid.d.ts +23 -0
  140. package/tokens/mermaid.js +181 -0
  141. package/tokens/mermaid.json +163 -0
  142. package/tokens/resolved.json +45 -1
  143. package/tokens/skins.js +3 -2
  144. package/tokens/tokens.dtcg.json +26 -0
  145. package/tokens/vega.d.ts +34 -0
  146. package/tokens/vega.js +155 -0
  147. package/tokens/vega.json +179 -0
  148. package/fonts/doto-400.ttf +0 -0
  149. package/fonts/doto-500.ttf +0 -0
  150. package/fonts/doto-600.ttf +0 -0
  151. package/fonts/doto-700.ttf +0 -0
  152. package/fonts/doto-800.ttf +0 -0
  153. package/fonts/doto-900.ttf +0 -0
package/docs/vega.md ADDED
@@ -0,0 +1,225 @@
1
+ # Vega-Lite
2
+
3
+ [Vega-Lite](https://vega.github.io/vega-lite/) is a **declarative JSON grammar
4
+ of graphics** — you describe a chart as data and it compiles (through
5
+ [Vega](https://vega.github.io/vega/)) to **SVG or canvas**. Like the
6
+ [Mermaid](./mermaid.md) and [D2](./d2.md) integrations, `@ponchia/ui` doesn't
7
+ render charts — it **themes** them from your tokens. Two things ship:
8
+
9
+ - `@ponchia/ui/vega` — `brontoVegaConfig(theme)`, the on-brand Vega-Lite
10
+ [`config`](https://vega.github.io/vega-lite/docs/config.html) object.
11
+ - `@ponchia/ui/vega.json` — the resolved per-theme config, for any consumer.
12
+
13
+ This is the idiomatic Vega theme shape — a `config`, the same kind the
14
+ [`vega-themes`](https://github.com/vega/vega-themes) package ships. Vega stays
15
+ the consumer's renderer; this is config only, and **Vega is not a dependency**
16
+ of bronto (the dev-only render-probe aside).
17
+
18
+ > Why Vega-Lite and not a bronto chart component? A chart needs **scales**
19
+ > (data → pixels) and **data binding** — the two things the analytical layer
20
+ > [refuses to own](./architecture.md). A spec is also something an
21
+ > LLM-from-another-system can emit as data, the same way it emits Mermaid/D2.
22
+ > So bronto themes a real charting grammar instead of shipping a fragile one.
23
+
24
+ ## Theme a chart
25
+
26
+ `brontoVegaConfig(theme)` returns a `config` object. Spread it into a spec, or
27
+ hand it to [vega-embed](https://github.com/vega/vega-embed):
28
+
29
+ ```js
30
+ import vegaEmbed from 'vega-embed';
31
+ import { brontoVegaConfig } from '@ponchia/ui/vega';
32
+
33
+ const theme = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
34
+
35
+ vegaEmbed('#chart', {
36
+ data: { values: [
37
+ { quarter: 'Q1', value: 42 },
38
+ { quarter: 'Q2', value: 58 },
39
+ { quarter: 'Q3', value: 50 },
40
+ ] },
41
+ mark: 'bar',
42
+ encoding: {
43
+ x: { field: 'quarter', type: 'nominal' },
44
+ y: { field: 'value', type: 'quantitative' },
45
+ },
46
+ }, { config: brontoVegaConfig(theme), renderer: 'svg', actions: false });
47
+ ```
48
+
49
+ Pass **`renderer: 'svg'`** (not vega-embed's `canvas` default): an SVG chart is
50
+ inspectable, themeable, survives the print/PDF pipeline, and is what the
51
+ [annotation layer](#annotate-a-chart) composes onto — a canvas chart prints as a
52
+ raster and carries no text alternative.
53
+
54
+ ### From a CDN, no bundler
55
+
56
+ Load Vega + Vega-Lite + vega-embed from **pinned `/build/*.min.js` UMD files**,
57
+ then pass the config. Pin exact versions and use the `/build/` path — a bare
58
+ `cdn.jsdelivr.net/npm/vega@6` redirect resolves to a module bundle that does
59
+ **not** register the global `window.vega`, so vega-embed throws and nothing
60
+ renders. Keep the three majors aligned: **Vega-Lite 6 targets Vega 6** (and
61
+ vega-embed 7), so don't mix a Vega-Lite 6 with a Vega 5 runtime:
62
+
63
+ ```html
64
+ <script src="https://cdn.jsdelivr.net/npm/vega@6.2.0/build/vega.min.js"></script>
65
+ <script src="https://cdn.jsdelivr.net/npm/vega-lite@6.4.3/build/vega-lite.min.js"></script>
66
+ <script src="https://cdn.jsdelivr.net/npm/vega-embed@7.1.0/build/vega-embed.min.js"></script>
67
+ <script>
68
+ // INLINE the config (copy the object for your theme from @ponchia/ui/vega.json).
69
+ // This is the only path that also works from a file:// report — see below.
70
+ const brontoLight = {
71
+ /* …paste tokens/vega.json → light here… */
72
+ };
73
+ vegaEmbed('#chart', spec, { config: brontoLight, renderer: 'svg', actions: false });
74
+ </script>
75
+ ```
76
+
77
+ > **file:// portability.** A report opened straight from disk (`file://`) cannot
78
+ > `import` the `@ponchia/ui/vega` module **nor** `fetch('…/vega.json')` — the
79
+ > browser blocks both across the `null`/file origin (CORS). So for a
80
+ > double-clickable or PDF-bound report, **inline the resolved config object**
81
+ > (as above) rather than fetching it. Over an `http(s)` origin (a dev server, a
82
+ > static host, a bundler), the `import { brontoVegaConfig }` form and a
83
+ > `fetch('https://cdn.jsdelivr.net/npm/@ponchia/ui@VERSION/tokens/vega.json')`
84
+ > both work — pin the package version in the URL, since the unversioned latest
85
+ > may predate this target.
86
+
87
+ For a build step or non-JS host, read `@ponchia/ui/vega.json` directly
88
+ (`{ light, dark }`, each a ready Vega-Lite `config`).
89
+
90
+ ### Why resolved colours, not `var(--x)`
91
+
92
+ Vega-Lite compiles a spec to a Vega scene that renders to **SVG or canvas** —
93
+ colours are **baked into the output** and parsed by `d3-color`, which understands
94
+ real hex/rgb but **not** `var()` (nor `oklch()`). So the config ships **resolved
95
+ hex per theme**, projected from the same token source as
96
+ [`tokens/resolved.json`](./architecture.md) / [`charts.json`](./theming.md).
97
+ Re-call `brontoVegaConfig()` when the theme toggles and re-embed.
98
+
99
+ ### What the slots paint
100
+
101
+ The config keeps a chart **monochrome by default** — the rationed accent is the
102
+ one chromatic default (series 1 / the lone mark), never the chrome:
103
+
104
+ | Slot | Paint | bronto token |
105
+ | --- | --- | --- |
106
+ | `background` | Chart canvas | `--bg` |
107
+ | `view.stroke` | Plot frame | `--line` |
108
+ | `mark.color` | Default / single-series mark | `--accent` |
109
+ | `rule.color` | Reference rules, annotations | `--line-strong` |
110
+ | `axis.domainColor` · `tickColor` | Axis line · ticks | `--line-strong` |
111
+ | `axis.gridColor` | Gridlines | `--line` |
112
+ | `axis.labelColor` · `titleColor` | Tick labels · axis title | `--text-soft` · `--text` |
113
+ | `text.color` | Free `text`/`label` marks | `--text` |
114
+ | `legend.*` · `header.*` · `title.*` | Legend, facet headers, title | `--text-soft` / `--text` / `--text-dim` |
115
+ | `*.font` / `*Font` | All text | `--sans` |
116
+ | `range.category` | 8-series categorical palette | `charts.json` categorical (series 1 = accent) |
117
+ | `range.ordinal` · `ramp` · `heatmap` | Single-hue sequential ramp | `charts.json` sequential |
118
+ | `range.diverging` | − … neutral … + ramp | `charts.json` diverging |
119
+
120
+ The palette is the same CVD-safe, pattern-paired set documented in
121
+ [theming](./theming.md#data-viz) — colour is never the sole channel. When a
122
+ series needs the redundant second channel, drive the mark's fill from the
123
+ `--chart-pattern-*` tokens or pair a [legend](./legends.md) swatch.
124
+
125
+ ### Spending the accent
126
+
127
+ Series 1 of `range.category` **is** the live accent, so a single-series chart and
128
+ the first category re-skin for free with `--accent`. To emphasise one mark in a
129
+ multi-series chart, paint just that mark with the accent and leave the rest
130
+ neutral — the same "reserve the accent for the one thing a reader must not miss"
131
+ rule the rest of the system follows. Two small helpers hand you the exact
132
+ per-theme hexes so you never hard-code a palette array index:
133
+
134
+ ```js
135
+ import { brontoVegaAccent, brontoVegaNeutral } from '@ponchia/ui/vega';
136
+
137
+ // e.g. a bar chart where only the 'Alert' category is loud:
138
+ const spec = {
139
+ /* …data… */
140
+ mark: 'bar',
141
+ encoding: {
142
+ x: { field: 'name', type: 'nominal' },
143
+ y: { field: 'value', type: 'quantitative' },
144
+ color: {
145
+ condition: { test: "datum.name === 'Alert'", value: brontoVegaAccent(theme) },
146
+ value: brontoVegaNeutral(theme),
147
+ },
148
+ legend: null,
149
+ },
150
+ };
151
+ ```
152
+
153
+ `brontoVegaAccent(theme)` is `range.category[0]` (the live accent) and
154
+ `brontoVegaNeutral(theme)` is the last category (the quiet neutral); re-read both
155
+ when the theme toggles. Prefer them over digging the hex out of
156
+ `tokens/resolved.json` — they are guaranteed to match the palette the config
157
+ already ships. In token terms the accent is `--chart-1` and the neutral is
158
+ `--chart-8`, so a [legend](./legends.md#swatch-colour) for an accent-rationed
159
+ chart keys those two series with `ui-legend__swatch--1` and
160
+ `ui-legend__swatch--8` — the swatches mirror the marks exactly.
161
+
162
+ ### Selecting the themed ramp in a spec
163
+
164
+ The config registers the ramps as **named ranges**, so a quantitative encoding
165
+ opts in with `scale: { range: 'heatmap' }` (or `'ramp'` / `'diverging'`) — the
166
+ range **name**, not a colour scheme:
167
+
168
+ ```js
169
+ {
170
+ mark: 'rect',
171
+ encoding: {
172
+ x: { field: 'x', type: 'nominal' },
173
+ y: { field: 'y', type: 'nominal' },
174
+ color: { field: 'v', type: 'quantitative', scale: { range: 'heatmap' } },
175
+ },
176
+ }
177
+ ```
178
+
179
+ > Use `scale: { range: 'heatmap' }`, **not** `scale: { scheme: 'heatmap' }`.
180
+ > `scheme:` looks up a registered Vega/d3 scheme by name and **throws** for
181
+ > `'heatmap'` (no such scheme) — the ramp is a custom `range` the bronto config
182
+ > defines, addressed by range name. A `quantitative` colour encoding already
183
+ > defaults to `range.heatmap`; name it explicitly only when a chart has several
184
+ > quantitative scales and you want a specific one (`'diverging'` for a signed
185
+ > domain around a neutral centre).
186
+
187
+ ### Sequential & diverging ramps invert by theme
188
+
189
+ `range.heatmap` / `ramp` / `ordinal` is a single-hue ramp that runs **pale → deep
190
+ as the value rises in light theme, and deep → pale in dark theme** (the bg flips,
191
+ so the ramp flips to stay legible against it). Two consequences:
192
+
193
+ - **Don't hard-code ink on a heatmap cell.** A fixed black (or white) label is
194
+ readable at one end of the ramp and invisible at the other — and the readable
195
+ end swaps between themes. Either omit per-cell labels and rely on the fallback
196
+ `ui-table`, or compute the label colour from the cell's luminance at render
197
+ time. bronto themes the ramp; it can't know your data domain, so it does not
198
+ ship a cell-ink helper.
199
+ - **A CSS gradient key won't pixel-match the Vega ramp.** A native
200
+ [`ui-legend--gradient`](./legends.md) track is interpolated in OKLCH; Vega
201
+ interpolates its `range.*` ramp in d3's RGB space. They share endpoints but
202
+ drift in the mid-tones, so a continuous gradient key placed beside a Vega
203
+ heatmap will not match its mid cells exactly. Use a **stepped** legend (one
204
+ swatch per band, each from the same `charts.json` ramp stop) when the key sits
205
+ next to the chart.
206
+
207
+ ## Annotate a chart
208
+
209
+ Vega renders to SVG, so the [annotation layer](./annotations.md) composes onto it
210
+ exactly as in the [Mermaid recipe](./mermaid.md#annotate-a-diagram): render to a
211
+ frozen SVG (vega-embed's `view.toSVG()`, or the Vega CLI), read the target mark's
212
+ box, and paste a `<g class="ui-annotation">` computed with
213
+ `@ponchia/ui/annotations`. The same caveat applies — Vega's internal SVG (element
214
+ ids, the `role`/`aria` structure, the scene transform) is **not a public
215
+ contract**, so pin your Vega version and key off the data, not generated ids.
216
+
217
+ ## Scope
218
+
219
+ bronto owns the theme config — gated structurally by `check:vega` (every colour
220
+ slot resolves, both themes, no `var()` leaks, every `range.*` ramp populated),
221
+ and separately a dev-only render-probe (`npm test`, via the `vega`/`vega-lite`
222
+ dev deps) asserts the colours actually land on a rendered chart — and the
223
+ annotation geometry. It does not own Vega's grammar, its rendering, or its internal SVG —
224
+ those stay Vega's, and the chart is a documented composition, not a shipped
225
+ runtime binding.
@@ -0,0 +1,78 @@
1
+ # Workbench — inspector, properties, selection bar
2
+
3
+ `@ponchia/ui/css/workbench.css` is an opt-in set of primitives for **real
4
+ tools**: an inspector panel, property rows for a selected object, and a bar of
5
+ actions on the current selection. Generic kits stop at cards/tables/forms, so
6
+ every app builds its own half-consistent workbench. This is the low-risk CSS
7
+ core — layout and affordances only.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/workbench.css';
12
+ ```
13
+
14
+ Resizable split panes (a focusable ARIA window-splitter behavior) and drag
15
+ handles are deliberately deferred until a consumer needs them. Not in the core
16
+ bundle.
17
+
18
+ ## Inspector — `.ui-inspector`
19
+
20
+ A panel of details for the selected object: a `__header` (title + actions) over
21
+ a `__body` of property rows.
22
+
23
+ ```html
24
+ <aside class="ui-inspector">
25
+ <div class="ui-inspector__head">
26
+ <h2 class="ui-eyebrow">Rectangle</h2>
27
+ <button class="ui-button ui-button--subtle ui-button--sm" type="button">Reset</button>
28
+ </div>
29
+ <div class="ui-inspector__body">
30
+ <!-- property rows -->
31
+ </div>
32
+ </aside>
33
+ ```
34
+
35
+ ## Property row — `.ui-property`
36
+
37
+ A label/value pair, denser than `ui-key-value` and tuned for an inspector. The
38
+ `__value` can hold a static read-out or an input.
39
+
40
+ ```html
41
+ <div class="ui-property">
42
+ <span class="ui-property__label">Width</span>
43
+ <span class="ui-property__value">240 px</span>
44
+ </div>
45
+ <div class="ui-property">
46
+ <span class="ui-property__label">Fill</span>
47
+ <span class="ui-property__value"><input class="ui-input" value="#121212" /></span>
48
+ </div>
49
+ ```
50
+
51
+ ## Selection bar — `.ui-selectionbar`
52
+
53
+ > **Name note:** `.ui-selectionbar` is the workbench bulk-action bar (this
54
+ > section). It is unrelated to the `.ui-sel--on` / `.ui-sel--off` /
55
+ > `.ui-sel--maybe` selection-emphasis state classes in
56
+ > [`css/selection.css`](./selection.md), which style host-managed selection
57
+ > state on individual items.
58
+
59
+ A raised bar of actions on the current selection: a `__count` on one side,
60
+ `__actions` on the other. The host owns what is selected and what the actions do.
61
+
62
+ ```html
63
+ <div class="ui-selectionbar">
64
+ <span class="ui-selectionbar__count">3 selected</span>
65
+ <span class="ui-selectionbar__actions">
66
+ <button class="ui-button ui-button--subtle ui-button--sm" type="button">Group</button>
67
+ <button class="ui-button ui-button--subtle ui-button--sm" type="button">Align</button>
68
+ <button class="ui-button ui-button--danger ui-button--sm" type="button">Delete</button>
69
+ </span>
70
+ </div>
71
+ ```
72
+
73
+ ## Scope
74
+
75
+ CSS only, no recipes — these are structural containers and rows; apply the
76
+ classes directly (or read them from `cls.inspector`, `cls.property`, …). Pair
77
+ the selection bar with the cross-cutting [`ui-sel`](./selection.md) states on the
78
+ selected items themselves; Bronto styles both, the host owns the hit-testing.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/glyphs/glyphs.js CHANGED
@@ -23,7 +23,7 @@ export const GLYPH_SIZE = 16;
23
23
 
24
24
  // Raw bitmaps. Each is GLYPH_SIZE rows of GLYPH_SIZE chars over [.#*]:
25
25
  // `.` off · `#` hot · `*` accent. Only `spark` uses accent dots — it is the
26
- // canonical two-tone demo; the gate in check-glyphs.mjs enforces the shape.
26
+ // canonical two-tone demo; test/glyphs.test.mjs asserts the spark-only-* rule.
27
27
  const RAW = {
28
28
  circle: [
29
29
  '................',
@@ -941,9 +941,9 @@ function esc(s) {
941
941
  .replace(/"/g, '&quot;');
942
942
  }
943
943
 
944
- // `dot`/`gap` land in an inline-CSS context (`style="--dotmatrix-dot:VALUE"`),
945
- // where HTML-escaping a `"` stops attribute breakout but a `;` would still open
946
- // a second CSS declaration (overlay/clickjacking, selector exfil). So restrict
944
+ // `dot`, `gap`, and `size` land in an inline-CSS context (`style=""`), where
945
+ // HTML-escaping a `"` stops attribute breakout but a `;` would still open a
946
+ // second CSS declaration (overlay/clickjacking, selector exfil). So restrict
947
947
  // them to length/calc syntax — digits, units, %, whitespace and `()+-*/.,` for
948
948
  // calc()/clamp()/var() — and drop anything else rather than emit it.
949
949
  function cssLen(v) {
@@ -1004,6 +1004,8 @@ function maskUrl(rows) {
1004
1004
  * bitmap (one DOM node, not GLYPH_SIZE²) — the icon-at-scale path: it sizes to
1005
1005
  * `size` (or `--icon-size` / `1em`) and inherits `currentColor`. The
1006
1006
  * cell-mode options (grid/solid/anim/dot/gap) don't apply; `label` does.
1007
+ * Mask mode is single-tone: accent `*` cells (used by `spark`) render
1008
+ * identically to hot `#` cells — both become opaque mask regions.
1007
1009
  * Needs `@ponchia/ui/css` (the `.ui-icon` rule).
1008
1010
  */
1009
1011
  export function renderGlyph(name, options = {}) {