@ponchia/ui 0.5.0 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +386 -4
- package/MIGRATIONS.json +14 -0
- package/README.md +29 -6
- package/annotations/index.d.ts +398 -276
- package/annotations/index.d.ts.map +1 -0
- package/annotations/index.js +350 -77
- package/behaviors/carousel.d.ts +28 -0
- package/behaviors/carousel.d.ts.map +1 -0
- package/behaviors/carousel.js +20 -16
- package/behaviors/combobox.d.ts +40 -0
- package/behaviors/combobox.d.ts.map +1 -0
- package/behaviors/combobox.js +111 -29
- package/behaviors/command.d.ts +41 -0
- package/behaviors/command.d.ts.map +1 -0
- package/behaviors/command.js +27 -15
- package/behaviors/connectors.d.ts +17 -0
- package/behaviors/connectors.d.ts.map +1 -0
- package/behaviors/connectors.js +7 -5
- package/behaviors/crosshair.d.ts +42 -0
- package/behaviors/crosshair.d.ts.map +1 -0
- package/behaviors/crosshair.js +23 -6
- package/behaviors/dialog.d.ts +20 -0
- package/behaviors/dialog.d.ts.map +1 -0
- package/behaviors/dialog.js +6 -2
- package/behaviors/disclosure.d.ts +10 -0
- package/behaviors/disclosure.d.ts.map +1 -0
- package/behaviors/disclosure.js +6 -2
- package/behaviors/dismissible.d.ts +10 -0
- package/behaviors/dismissible.d.ts.map +1 -0
- package/behaviors/dismissible.js +6 -2
- package/behaviors/forms.d.ts +27 -0
- package/behaviors/forms.d.ts.map +1 -0
- package/behaviors/forms.js +54 -13
- package/behaviors/glyph.d.ts +14 -0
- package/behaviors/glyph.d.ts.map +1 -0
- package/behaviors/glyph.js +28 -5
- package/behaviors/index.d.ts +31 -237
- package/behaviors/index.d.ts.map +1 -0
- package/behaviors/index.js +17 -0
- package/behaviors/inert.d.ts +20 -0
- package/behaviors/inert.d.ts.map +1 -0
- package/behaviors/inert.js +46 -0
- package/behaviors/internal.d.ts +25 -0
- package/behaviors/internal.d.ts.map +1 -0
- package/behaviors/internal.js +77 -1
- package/behaviors/legend.d.ts +35 -0
- package/behaviors/legend.d.ts.map +1 -0
- package/behaviors/legend.js +32 -2
- package/behaviors/menu.d.ts +16 -0
- package/behaviors/menu.d.ts.map +1 -0
- package/behaviors/menu.js +6 -2
- package/behaviors/modal.d.ts +41 -0
- package/behaviors/modal.d.ts.map +1 -0
- package/behaviors/modal.js +124 -0
- package/behaviors/popover.d.ts +28 -0
- package/behaviors/popover.d.ts.map +1 -0
- package/behaviors/popover.js +78 -7
- package/behaviors/spotlight.d.ts +17 -0
- package/behaviors/spotlight.d.ts.map +1 -0
- package/behaviors/spotlight.js +7 -5
- package/behaviors/table.d.ts +36 -0
- package/behaviors/table.d.ts.map +1 -0
- package/behaviors/table.js +84 -17
- package/behaviors/tabs.d.ts +20 -0
- package/behaviors/tabs.d.ts.map +1 -0
- package/behaviors/tabs.js +17 -14
- package/behaviors/theme.d.ts +54 -0
- package/behaviors/theme.d.ts.map +1 -0
- package/behaviors/theme.js +22 -3
- package/behaviors/toast.d.ts +49 -0
- package/behaviors/toast.d.ts.map +1 -0
- package/behaviors/toast.js +47 -3
- package/classes/classes.json +2527 -0
- package/classes/index.d.ts +134 -15
- package/classes/index.js +280 -80
- package/classes/vscode.css-custom-data.json +12 -0
- package/connectors/index.d.ts +201 -69
- package/connectors/index.d.ts.map +1 -0
- package/connectors/index.js +142 -25
- package/css/app.css +69 -13
- package/css/base.css +15 -10
- package/css/bullet.css +108 -0
- package/css/code.css +98 -0
- package/css/connectors.css +17 -0
- package/css/content.css +22 -3
- package/css/crosshair.css +7 -7
- package/css/dataviz.css +5 -1
- package/css/diff.css +153 -0
- package/css/disclosure.css +53 -7
- package/css/dots.css +94 -7
- package/css/feedback.css +97 -7
- package/css/forms.css +113 -4
- package/css/legend.css +16 -9
- package/css/marks.css +38 -8
- package/css/motion.css +98 -53
- package/css/navigation.css +7 -0
- package/css/overlay.css +90 -3
- package/css/primitives.css +158 -13
- package/css/report.css +73 -56
- package/css/sidenote.css +67 -0
- package/css/site.css +16 -2
- package/css/sources.css +43 -1
- package/css/spark.css +62 -0
- package/css/spotlight.css +1 -1
- package/css/table.css +9 -2
- package/css/term.css +110 -0
- package/css/textref.css +63 -0
- package/css/toc.css +91 -0
- package/css/tokens.css +49 -1
- package/css/tree.css +134 -0
- package/css/workbench.css +1 -1
- package/dist/bronto.css +1 -1
- package/dist/css/analytical.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/base.css +1 -1
- package/dist/css/bullet.css +1 -0
- package/dist/css/code.css +1 -0
- package/dist/css/connectors.css +1 -1
- package/dist/css/content.css +1 -1
- package/dist/css/crosshair.css +1 -1
- package/dist/css/diff.css +1 -0
- package/dist/css/disclosure.css +1 -1
- package/dist/css/dots.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/forms.css +1 -1
- package/dist/css/legend.css +1 -1
- package/dist/css/marks.css +1 -1
- package/dist/css/motion.css +1 -1
- package/dist/css/navigation.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/report.css +1 -1
- package/dist/css/sidenote.css +1 -0
- package/dist/css/site.css +1 -1
- package/dist/css/sources.css +1 -1
- package/dist/css/spark.css +1 -0
- package/dist/css/spotlight.css +1 -1
- package/dist/css/table.css +1 -1
- package/dist/css/term.css +1 -0
- package/dist/css/textref.css +1 -0
- package/dist/css/toc.css +1 -0
- package/dist/css/tokens.css +1 -1
- package/dist/css/tree.css +1 -0
- package/dist/css/workbench.css +1 -1
- package/docs/adr/0003-theme-model.md +1 -1
- package/docs/annotations.md +133 -14
- package/docs/architecture.md +49 -6
- package/docs/bullet.md +78 -0
- package/docs/code.md +76 -0
- package/docs/contrast.md +116 -92
- package/docs/d2.md +196 -0
- package/docs/diff.md +146 -0
- package/docs/legends.md +23 -3
- package/docs/marks.md +9 -2
- package/docs/mermaid.md +169 -0
- package/docs/reference.md +201 -26
- package/docs/reporting.md +416 -57
- package/docs/sidenote.md +64 -0
- package/docs/sources.md +27 -0
- package/docs/spark.md +78 -0
- package/docs/stability.md +10 -2
- package/docs/term.md +81 -0
- package/docs/textref.md +78 -0
- package/docs/theming.md +44 -5
- package/docs/toc.md +83 -0
- package/docs/tree.md +74 -0
- package/docs/usage.md +354 -16
- package/docs/vega.md +244 -0
- package/docs/workbench.md +7 -1
- package/glyphs/glyphs.js +13 -5
- package/llms.txt +285 -14
- package/package.json +95 -17
- package/qwik/index.d.ts +44 -59
- package/qwik/index.d.ts.map +1 -0
- package/qwik/index.js +65 -3
- package/react/index.d.ts +41 -61
- package/react/index.d.ts.map +1 -0
- package/react/index.js +63 -3
- package/solid/index.d.ts +68 -61
- package/solid/index.d.ts.map +1 -0
- package/solid/index.js +66 -3
- package/tokens/d2.d.ts +38 -0
- package/tokens/d2.js +71 -0
- package/tokens/d2.json +43 -0
- package/tokens/index.d.ts +5 -5
- package/tokens/index.js +15 -1
- package/tokens/index.json +9 -0
- package/tokens/mermaid.d.ts +23 -0
- package/tokens/mermaid.js +181 -0
- package/tokens/mermaid.json +163 -0
- package/tokens/resolved.json +45 -1
- package/tokens/skins.js +3 -2
- package/tokens/tokens.dtcg.json +26 -0
- package/tokens/vega.d.ts +34 -0
- package/tokens/vega.js +155 -0
- package/tokens/vega.json +179 -0
package/docs/reporting.md
CHANGED
|
@@ -15,10 +15,17 @@ rewrites them:
|
|
|
15
15
|
@import '@ponchia/ui/css/legend.css';
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
**`dist/bronto.css` is the standard component set only — it does NOT contain
|
|
19
|
+
the report, chart, annotation, or legend layers.** Those are opt-in leaves
|
|
20
|
+
under `dist/css/`; a report links the default bundle *and* each leaf it uses.
|
|
21
|
+
Forgetting them is the most common way an LLM-emitted report renders unstyled.
|
|
22
|
+
|
|
18
23
|
For standalone browser HTML, use real stylesheet URLs. Package specifiers like
|
|
19
|
-
`@ponchia/ui/css/report.css` do not resolve in a saved `.html` file
|
|
24
|
+
`@ponchia/ui/css/report.css` do not resolve in a saved `.html` file — and note
|
|
25
|
+
the path is `dist/css/`, the built leaf, not the source `css/`:
|
|
20
26
|
|
|
21
27
|
```html
|
|
28
|
+
<!-- installed locally -->
|
|
22
29
|
<link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/bronto.css" />
|
|
23
30
|
<link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/report.css" />
|
|
24
31
|
<link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/dataviz.css" />
|
|
@@ -26,16 +33,51 @@ For standalone browser HTML, use real stylesheet URLs. Package specifiers like
|
|
|
26
33
|
<link rel="stylesheet" href="./node_modules/@ponchia/ui/dist/css/legend.css" />
|
|
27
34
|
```
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
No install? Link the same files from a CDN. Pin the version — pre-1.0, breaking
|
|
37
|
+
changes ship in the minor (see [stability.md](./stability.md)):
|
|
38
|
+
|
|
39
|
+
```html
|
|
40
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.3/dist/bronto.css" />
|
|
41
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.3/dist/css/report.css" />
|
|
42
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.3/dist/css/dataviz.css" />
|
|
43
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.3/dist/css/annotations.css" />
|
|
44
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ponchia/ui@0.6.3/dist/css/legend.css" />
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The CDN serves the package's own `fonts/` next to the CSS, so font URLs resolve
|
|
48
|
+
with no extra setup. If instead you copy the built CSS next to the report, keep
|
|
49
|
+
the same relationship between `dist/bronto.css`, the `dist/css/*` leaves, and
|
|
50
|
+
`fonts/` so the relative font URLs continue to resolve.
|
|
33
51
|
|
|
34
52
|
The report layer is static and PDF-first. It does not initialize behaviors and
|
|
35
53
|
does not sanitize content. If a report includes arbitrary LLM, CMS, or user HTML,
|
|
36
54
|
sanitize that content before rendering it and do not initialize
|
|
37
55
|
`data-bronto-*` behaviors on the generated region.
|
|
38
56
|
|
|
57
|
+
## The analytical toolbox in a report
|
|
58
|
+
|
|
59
|
+
`css/report.css` gives you the document grammar (covers, sections, findings,
|
|
60
|
+
evidence). The _content_ inside those sections is where the rest of the
|
|
61
|
+
analytical layer earns its place. Each one is an opt-in import that stays out of
|
|
62
|
+
the default bundle — add the leaves a given report actually needs, or pull the
|
|
63
|
+
whole set with `@ponchia/ui/css/analytical.css`. Reach for:
|
|
64
|
+
|
|
65
|
+
| Layer | Import | Reach for it when… |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| **Marks** (`.ui-mark`, `.ui-bracket-note`) | `css/marks.css` | You want to emphasise a phrase _in prose_ — a highlight on the finding, an underline on a risk, or a bracket around an evidence/caveat passage. The inline counterpart to annotations. See [marks.md](./marks.md). |
|
|
68
|
+
| **Sources / provenance** (`.ui-citation`, `.ui-source-card`, `.ui-source-list`, `.ui-provenance`) | `css/sources.css` | The report makes claims a reader will question — "where did this come from?". A CSS-only trust layer whose cross-cutting state modifier (`.ui-src--verified`, plus reviewed / generated / unverified / stale / conflict) sets a rationed tone, always paired with a written label, never colour alone. See [sources.md](./sources.md). |
|
|
69
|
+
| **Annotations** (`.ui-annotation*`) | `css/annotations.css` | A figure needs an explicit callout — a peak, a limit, a watched region — or a small decorative margin mark. SVG only. See [annotations.md](./annotations.md) and the [off-chart + scaling notes](./annotations.md#using-annotations-off-chart) before you size one. |
|
|
70
|
+
| **Legends / data keys** (`.ui-legend*`) | `css/legend.css` | A chart figure needs a colour key. WCAG 1.4.1 by construction. See [legends.md](./legends.md). |
|
|
71
|
+
| **Mermaid theme** (`@ponchia/ui/mermaid`) | _(JS/JSON, no CSS)_ | The report embeds a [Mermaid](https://mermaid.js.org) diagram (flowchart, sequence, pie…) and you want it on-brand instead of generic. A resolved `base` theme projected from the same tokens as `charts.json`; annotate the rendered SVG with the annotation layer. See [mermaid.md](./mermaid.md). |
|
|
72
|
+
| **D2 theme** (`@ponchia/ui/d2`) | _(JS/JSON, no CSS)_ | The report embeds a [D2](https://d2lang.com) diagram and you want it on-brand. Resolved theme-override slots (monochrome base + one rationed accent) projected from the same tokens; annotate the rendered SVG. See [d2.md](./d2.md). |
|
|
73
|
+
| **Generated-content trust** (`.ui-generated`, `.ui-origin-label`, `.ui-reasoning`, `.ui-tool-log`) | `css/generated.css` | The report (or a section of it) is AI/system-authored and should _say so_ — an origin label plus quiet, collapsible reasoning / tool-call logs. Pairs with the sources layer. See [generated.md](./generated.md). |
|
|
74
|
+
| **Lifecycle / system state** (`.ui-state`, `.ui-syncbar`) | `css/state.css` | A status report needs to show the state a thing is in — saving / queued / stale / conflict / reviewed — as a labelled object, not a bare coloured dot. See [state.md](./state.md). |
|
|
75
|
+
|
|
76
|
+
These compose with the report-native primitives already called out in
|
|
77
|
+
[Composition rules](#composition-rules): `ui-statgrid`, `ui-alert`, `ui-table`,
|
|
78
|
+
`ui-timeline`, `ui-meter`, and `ui-num`. None of them require behavior JS, so
|
|
79
|
+
they are all safe in the static, PDF-first report path.
|
|
80
|
+
|
|
39
81
|
## Canonical skeleton
|
|
40
82
|
|
|
41
83
|
```html
|
|
@@ -94,7 +136,7 @@ sanitize that content before rendering it and do not initialize
|
|
|
94
136
|
below, which only shrinks the cover.
|
|
95
137
|
- Use `ui-report__cover` for title, subtitle, author/date, and generation
|
|
96
138
|
metadata. Add `ui-report__cover--compact` for short screen-first reports.
|
|
97
|
-
Use `ui-
|
|
139
|
+
Use `ui-report__head` for a compact in-page header instead of a full cover
|
|
98
140
|
(same role, no tall hero block). Author `ui-report__meta` as a `<ul>` — the
|
|
99
141
|
facts it lists are unordered.
|
|
100
142
|
- Use `ui-report__section` and `ui-report__section-head` for report chapters.
|
|
@@ -125,23 +167,198 @@ sanitize that content before rendering it and do not initialize
|
|
|
125
167
|
inside `ui-prose`; use `.ui-table` for curated evidence tables. If a
|
|
126
168
|
`ui-report__evidence` block contains only a `ui-table-wrap`, the report layer
|
|
127
169
|
removes the inner frame so evidence tables do not look double-boxed.
|
|
128
|
-
- Every `<figure>` should include a `figcaption` using
|
|
129
|
-
`ui-chart__caption` (chart figures) or `ui-report__caption` (any other
|
|
130
|
-
report figure); the two are interchangeable in style.
|
|
170
|
+
- Every `<figure>` should include a `figcaption` using `ui-report__caption`.
|
|
131
171
|
- Do not use raw color values. Theme with `--accent`; use status tones for
|
|
132
172
|
status; use chart tokens only in chart figures.
|
|
133
173
|
|
|
174
|
+
## Numbers and dates
|
|
175
|
+
|
|
176
|
+
The framework **aligns** figures; it does not **format** them. `.is-num` (table
|
|
177
|
+
cells) and `ui-num` (standalone) give tabular figures and end-alignment so a
|
|
178
|
+
column lines up — but a raw `1240` or `2026-6-1` still reads as machine output.
|
|
179
|
+
Format the values yourself, before they reach the markup:
|
|
180
|
+
|
|
181
|
+
- **Numbers** — group thousands and fix the precision. In JS,
|
|
182
|
+
`new Intl.NumberFormat('en-US').format(1240)` → `1,240`;
|
|
183
|
+
`{ style: 'currency', currency: 'USD' }` → `$1,240.00`;
|
|
184
|
+
`{ style: 'percent', maximumFractionDigits: 1 }` for rates;
|
|
185
|
+
`{ notation: 'compact' }` → `1.2M` for headline KPIs. Pick one locale and
|
|
186
|
+
precision per report and apply it consistently.
|
|
187
|
+
- **Dates** — render a human label but keep the machine value in a
|
|
188
|
+
`<time datetime="…">` (ISO-8601), as the report skeleton and `ui-timeline`
|
|
189
|
+
already do. `new Intl.DateTimeFormat('en-US', { dateStyle: 'medium' })`
|
|
190
|
+
→ `Jun 1, 2026`.
|
|
191
|
+
- **Signs and units** — write the sign and unit into the text (`+1`, `−3.2%`,
|
|
192
|
+
`18 h`); never rely on a tone or arrow alone to say "down" (WCAG 1.4.1).
|
|
193
|
+
- Keep a column's precision uniform so the tabular alignment actually reads.
|
|
194
|
+
|
|
195
|
+
A non-JS host formats with its own locale library (Python `babel`, Go
|
|
196
|
+
`golang.org/x/text/message`, etc.) and emits the finished strings — bronto only
|
|
197
|
+
styles them.
|
|
198
|
+
|
|
199
|
+
## Trend deltas
|
|
200
|
+
|
|
201
|
+
For a standalone change indicator (a KPI's movement, a row's quarter-over-quarter
|
|
202
|
+
shift), use `ui-delta`. A direction modifier sets both the arrow glyph — the
|
|
203
|
+
non-colour channel — and the conventional tone:
|
|
204
|
+
|
|
205
|
+
```html
|
|
206
|
+
<span class="ui-delta ui-delta--up">+12.4%</span>
|
|
207
|
+
<span class="ui-delta ui-delta--down">−3</span>
|
|
208
|
+
<span class="ui-delta ui-delta--flat">0</span>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Direction tone follows the common case (up = good, green; down = bad, red). When
|
|
212
|
+
**up is the bad direction** — latency, error rate, cost, churn — add
|
|
213
|
+
`ui-delta--invert` to swap only the tone; the arrow still reports real direction:
|
|
214
|
+
|
|
215
|
+
```html
|
|
216
|
+
<span class="ui-delta ui-delta--up ui-delta--invert">+48 ms p95</span>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The arrow is visual; always include the number and unit in the text.
|
|
220
|
+
|
|
221
|
+
A stat card's own `ui-stat__delta is-pos` / `is-neg` carries **tone** (good vs
|
|
222
|
+
bad, as colour) and auto-prepends a `▲`/`▼` glyph — but that glyph follows the
|
|
223
|
+
**tone**, not the real direction: `is-pos` always renders `▲` and `is-neg`
|
|
224
|
+
always `▼`, regardless of whether the number went up or down. The tone is
|
|
225
|
+
deliberately decoupled from direction (a *dropped* latency is good, so `is-pos`),
|
|
226
|
+
so the auto-arrow can contradict the words: write `is-pos` on a "−48 ms" and it
|
|
227
|
+
prints `▲ −48 ms`. Do **not** add your own direction word/arrow on top, or you
|
|
228
|
+
double it. When the change must read by direction — most printed reports —
|
|
229
|
+
prefer `ui-delta` for the card's delta line, because its `--up`/`--down` arrow is
|
|
230
|
+
a real (author-controlled) non-colour channel; reach for the arrow-free `ui-num`
|
|
231
|
+
if you want neither tone nor glyph:
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<span class="ui-stat__label">p95 latency</span>
|
|
235
|
+
<span class="ui-stat__value">172 ms</span>
|
|
236
|
+
<span class="ui-delta ui-delta--down ui-delta--invert">−48 ms</span>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Here the arrow points **down** (latency fell) while `--invert` keeps the tone
|
|
240
|
+
green (down is good) — both channels stay honest. Reach for `ui-stat__delta` only
|
|
241
|
+
when a bare tonal accent is enough.
|
|
242
|
+
|
|
243
|
+
## Comparison layout
|
|
244
|
+
|
|
245
|
+
For an "A vs B" or before/after section, use `ui-compare` — a fluid grid that
|
|
246
|
+
wraps to a single stack on a narrow screen, so two panels never overflow.
|
|
247
|
+
`ui-compare__col` is one side; label it with `ui-compare__head`. Add
|
|
248
|
+
`ui-compare--2up` to pin exactly two equal columns for a hard pairing.
|
|
249
|
+
|
|
250
|
+
```html
|
|
251
|
+
<div class="ui-compare ui-compare--2up">
|
|
252
|
+
<div class="ui-compare__col">
|
|
253
|
+
<p class="ui-compare__head">Before</p>
|
|
254
|
+
<div class="ui-statgrid">
|
|
255
|
+
<div class="ui-stat">
|
|
256
|
+
<span class="ui-stat__label">p95 latency</span>
|
|
257
|
+
<span class="ui-stat__value">220 ms</span>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
<div class="ui-compare__col">
|
|
262
|
+
<p class="ui-compare__head">After</p>
|
|
263
|
+
<div class="ui-statgrid">
|
|
264
|
+
<div class="ui-stat">
|
|
265
|
+
<span class="ui-stat__label">p95 latency</span>
|
|
266
|
+
<span class="ui-stat__value">172 ms</span>
|
|
267
|
+
<span class="ui-stat__delta is-pos">−48 ms</span>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
</div>
|
|
272
|
+
```
|
|
273
|
+
|
|
134
274
|
## Chart figure recipe
|
|
135
275
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
276
|
+
bronto ships **no chart component** — a chart needs scales and data binding, the
|
|
277
|
+
two things the analytical layer [refuses to own](./architecture.md). It supplies
|
|
278
|
+
the figure frame, the data key, and the colour palette; the chart itself comes
|
|
279
|
+
from one of two routes:
|
|
280
|
+
|
|
281
|
+
- **Live, interactive, or many-series** — theme [Vega-Lite](./vega.md):
|
|
282
|
+
`brontoVegaConfig(theme)` (from `@ponchia/ui/vega`) returns an on-brand
|
|
283
|
+
Vega-Lite `config` you spread into a spec and hand to vega-embed. Vega renders
|
|
284
|
+
the SVG/canvas; bronto only paints it. See [vega.md](./vega.md) for the full
|
|
285
|
+
recipe and the resolved `@ponchia/ui/vega.json` for non-JS hosts.
|
|
286
|
+
- **Frozen, print, or zero-JS** — hand-author a token-themed inline `<svg>`,
|
|
287
|
+
painting marks from the `--chart-N` palette so the figure prints exactly and
|
|
288
|
+
carries no runtime.
|
|
289
|
+
|
|
290
|
+
Whichever route, the figure frame is the same: wrap it in `ui-report__figure`,
|
|
291
|
+
caption it with `ui-report__caption`, give it the standalone, portable
|
|
292
|
+
`.ui-legend` data key (`@ponchia/ui/css/legend.css` — see
|
|
293
|
+
[legends.md](./legends.md)), and pair every colour with a direct label, a
|
|
294
|
+
pattern, **and** a fallback `ui-table` so the figure survives mono print and
|
|
295
|
+
colour-vision deficiency.
|
|
296
|
+
|
|
297
|
+
A Vega-Lite figure. The live mount is **`ui-screen-only`** and the fallback
|
|
298
|
+
`ui-table` carries the data into print — a live chart bakes the on-screen theme
|
|
299
|
+
into its SVG/canvas at render time, so printing it would emit a dark-baked chart
|
|
300
|
+
on white paper. Print the table; keep the chart for screen:
|
|
301
|
+
|
|
302
|
+
```html
|
|
303
|
+
<figure class="ui-report__figure" role="group" aria-labelledby="chart-title">
|
|
304
|
+
<figcaption id="chart-title" class="ui-report__caption">
|
|
305
|
+
Fig 1 - Weekly focus split
|
|
306
|
+
</figcaption>
|
|
307
|
+
<div id="focus-chart" class="ui-screen-only" style="min-block-size: 240px">
|
|
308
|
+
<noscript>Chart needs JavaScript — the data is in the table below.</noscript>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="ui-table-wrap">
|
|
311
|
+
<table class="ui-table ui-table--dense">
|
|
312
|
+
<caption>Chart source data</caption>
|
|
313
|
+
<thead>
|
|
314
|
+
<tr><th>Series</th><th class="is-num">Hours</th></tr>
|
|
315
|
+
</thead>
|
|
316
|
+
<tbody>
|
|
317
|
+
<tr><td>Research</td><td class="is-num">18</td></tr>
|
|
318
|
+
<tr><td>Delivery</td><td class="is-num">11</td></tr>
|
|
319
|
+
</tbody>
|
|
320
|
+
</table>
|
|
321
|
+
</div>
|
|
322
|
+
</figure>
|
|
323
|
+
<script type="module">
|
|
324
|
+
import vegaEmbed from 'vega-embed';
|
|
325
|
+
import { brontoVegaConfig } from '@ponchia/ui/vega';
|
|
326
|
+
const theme = document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light';
|
|
327
|
+
vegaEmbed('#focus-chart', {
|
|
328
|
+
data: { values: [
|
|
329
|
+
{ series: 'Research', hours: 18 },
|
|
330
|
+
{ series: 'Delivery', hours: 11 },
|
|
331
|
+
] },
|
|
332
|
+
mark: 'bar',
|
|
333
|
+
encoding: {
|
|
334
|
+
x: { field: 'series', type: 'nominal' },
|
|
335
|
+
y: { field: 'hours', type: 'quantitative' },
|
|
336
|
+
},
|
|
337
|
+
}, { config: brontoVegaConfig(theme), renderer: 'svg', actions: false });
|
|
338
|
+
</script>
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Give the mount a `min-block-size` so the figure is not a collapsed blank band
|
|
342
|
+
before the chart paints (or while JS is blocked), and pass **`renderer: 'svg'`**
|
|
343
|
+
(vega-embed defaults to `canvas`, which doesn't theme-inspect and prints as a
|
|
344
|
+
raster).
|
|
345
|
+
|
|
346
|
+
> The bare-specifier `import`s above assume a **build step** (or an import map),
|
|
347
|
+
> and even then `import`/`fetch` of the config only works over an `http(s)`
|
|
348
|
+
> origin. A static report **opened from disk (`file://`) cannot import the module
|
|
349
|
+
> nor fetch `vega.json`** (CORS) — load Vega + Vega-Lite + vega-embed from pinned
|
|
350
|
+
> `/build/*.min.js` CDN tags and **inline the resolved `config` object**
|
|
351
|
+
> (generate it with `npm run emit:theme vega light`), the file://-safe recipe in
|
|
352
|
+
> [vega.md](./vega.md#from-a-cdn-no-bundler). For a report
|
|
353
|
+
> you intend to **print/PDF**, prefer the frozen inline `<svg>` below — it has no
|
|
354
|
+
> runtime, prints exactly, and sidesteps all of this.
|
|
355
|
+
|
|
356
|
+
A frozen, token-themed inline `<svg>` for the same data — no runtime, prints
|
|
357
|
+
exactly, with a `.ui-legend` key and the fallback table:
|
|
141
358
|
|
|
142
359
|
```html
|
|
143
|
-
<figure class="ui-report__figure ui-
|
|
144
|
-
<figcaption id="chart-title" class="ui-
|
|
360
|
+
<figure class="ui-report__figure ui-print-exact" role="group" aria-labelledby="chart-title">
|
|
361
|
+
<figcaption id="chart-title" class="ui-report__caption">
|
|
145
362
|
Fig 1 - Weekly focus split
|
|
146
363
|
</figcaption>
|
|
147
364
|
<ul class="ui-legend" aria-label="Series">
|
|
@@ -162,42 +379,43 @@ fallback table.
|
|
|
162
379
|
<span class="ui-legend__label">Delivery</span>
|
|
163
380
|
</li>
|
|
164
381
|
</ul>
|
|
165
|
-
<
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<div class="ui-table-wrap">
|
|
183
|
-
<table class="ui-table ui-table--dense">
|
|
184
|
-
<caption>Chart source data</caption>
|
|
185
|
-
<thead>
|
|
186
|
-
<tr><th>Series</th><th class="is-num">Hours</th></tr>
|
|
187
|
-
</thead>
|
|
188
|
-
<tbody>
|
|
189
|
-
<tr><td>Research</td><td class="is-num">18</td></tr>
|
|
190
|
-
<tr><td>Delivery</td><td class="is-num">11</td></tr>
|
|
191
|
-
</tbody>
|
|
192
|
-
</table>
|
|
193
|
-
</div>
|
|
382
|
+
<svg viewBox="0 0 360 160" role="img" aria-labelledby="focus-svg-title">
|
|
383
|
+
<title id="focus-svg-title">Weekly focus split</title>
|
|
384
|
+
<line x1="36" y1="132" x2="324" y2="132" stroke="var(--line)" />
|
|
385
|
+
<rect x="72" y="42" width="96" height="90" fill="var(--chart-1)" />
|
|
386
|
+
<rect x="200" y="77" width="96" height="55" fill="var(--chart-2)" />
|
|
387
|
+
</svg>
|
|
388
|
+
<div class="ui-table-wrap">
|
|
389
|
+
<table class="ui-table ui-table--dense">
|
|
390
|
+
<caption>Chart source data</caption>
|
|
391
|
+
<thead>
|
|
392
|
+
<tr><th>Series</th><th class="is-num">Hours</th></tr>
|
|
393
|
+
</thead>
|
|
394
|
+
<tbody>
|
|
395
|
+
<tr><td>Research</td><td class="is-num">18</td></tr>
|
|
396
|
+
<tr><td>Delivery</td><td class="is-num">11</td></tr>
|
|
397
|
+
</tbody>
|
|
398
|
+
</table>
|
|
194
399
|
</div>
|
|
195
400
|
</figure>
|
|
196
401
|
```
|
|
197
402
|
|
|
198
|
-
For
|
|
199
|
-
|
|
200
|
-
|
|
403
|
+
For a frozen figure, drive the SVG fills from the `--chart-N` palette tokens
|
|
404
|
+
directly; for a Vega chart, the same colours arrive through
|
|
405
|
+
`brontoVegaConfig`'s `range.*` ramps, projected from `@ponchia/ui/charts.json`.
|
|
406
|
+
|
|
407
|
+
For a **sequential** figure (a heatmap, a choropleth, a magnitude ramp) fill the
|
|
408
|
+
cells from the single-hue ramp tokens `--chart-seq-1` … `--chart-seq-6`
|
|
409
|
+
(low → high); for a **diverging** figure (−…0…+) use `--chart-div-1` …
|
|
410
|
+
`--chart-div-7` (the middle band is the neutral midpoint). Both ramps live in
|
|
411
|
+
`css/dataviz.css`, and their resolved per-theme hexes are in
|
|
412
|
+
`@ponchia/ui/charts.json` (`sequential` / `diverging`) for a non-JS or `file://`
|
|
413
|
+
host that needs literal values. The same ramps back Vega's
|
|
414
|
+
`range.heatmap`/`ramp`/`diverging`, so a frozen sequential figure and a live Vega
|
|
415
|
+
heatmap read identically — but note the ramp **runs pale→deep in light theme and
|
|
416
|
+
deep→pale in dark** (it flips to stay legible against the background), so don't
|
|
417
|
+
print a colour key that assumes one direction, and don't bake a fixed ink colour
|
|
418
|
+
onto the cells. Pair the figure with a stepped legend and the fallback table.
|
|
201
419
|
|
|
202
420
|
## Annotation recipe
|
|
203
421
|
|
|
@@ -246,6 +464,111 @@ style itself.
|
|
|
246
464
|
</ol>
|
|
247
465
|
```
|
|
248
466
|
|
|
467
|
+
## Meters and quotes
|
|
468
|
+
|
|
469
|
+
Two more report-native primitives. A **meter** shows a measured value as a
|
|
470
|
+
proportion — set the percentage on the `--value` custom property of
|
|
471
|
+
`ui-meter__fill` (a number 0–100, see `classes.json` `customProperties`); a tone
|
|
472
|
+
modifier picks the fill colour, and the readable label is yours to write beside
|
|
473
|
+
it (never rely on the bar alone — WCAG 1.4.1):
|
|
474
|
+
|
|
475
|
+
```html
|
|
476
|
+
<div class="ui-meter ui-meter--accent">
|
|
477
|
+
<span class="ui-meter__fill" style="--value: 72"></span>
|
|
478
|
+
</div>
|
|
479
|
+
<span class="ui-num">72% of quota</span>
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
`--value` is a percentage and the fill **clamps at 100**, so an over-target
|
|
483
|
+
reading (e.g. 112 % of plan) shows a full bar — put the true figure in the
|
|
484
|
+
written label beside it (`ui-num`), which is the data of record anyway.
|
|
485
|
+
|
|
486
|
+
For a block of meters — SLO burn, error budgets, capacity — lay each out as
|
|
487
|
+
**label | bar | value** with `ui-meter__row`; `ui-meter__label` names it and
|
|
488
|
+
`ui-meter__value` carries the reading (the data of record — never the bar
|
|
489
|
+
alone). The row collapses to a stack on a narrow screen, so you don't hand-roll
|
|
490
|
+
the grid:
|
|
491
|
+
|
|
492
|
+
```html
|
|
493
|
+
<div class="ui-meter__row">
|
|
494
|
+
<span class="ui-meter__label">Write availability</span>
|
|
495
|
+
<div class="ui-meter ui-meter--danger" role="presentation">
|
|
496
|
+
<span class="ui-meter__fill" style="--value: 90"></span>
|
|
497
|
+
</div>
|
|
498
|
+
<span class="ui-meter__value ui-num">90% of budget burned</span>
|
|
499
|
+
</div>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
A **pull-quote** lifts a source sentence out of the prose — `ui-quote` is the
|
|
503
|
+
block, `ui-quote__cite` attributes it:
|
|
504
|
+
|
|
505
|
+
```html
|
|
506
|
+
<blockquote class="ui-quote">
|
|
507
|
+
<p>The migration paid for itself within the first billing cycle.</p>
|
|
508
|
+
<cite class="ui-quote__cite">Q2 finance review</cite>
|
|
509
|
+
</blockquote>
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Theming a live report
|
|
513
|
+
|
|
514
|
+
The report layer is static, but a screen report often offers a light/dark
|
|
515
|
+
**toggle**. The chart palette is the one piece that does not re-skin from CSS
|
|
516
|
+
alone: Vega and Mermaid/D2 bake resolved colours into their SVG at render time
|
|
517
|
+
(they can't read `var()`), so on a theme flip you must **re-render** the foreign
|
|
518
|
+
figures. Set the theme on the root and re-embed:
|
|
519
|
+
|
|
520
|
+
```html
|
|
521
|
+
<button type="button" class="ui-button ui-button--ghost ui-button--sm" id="theme-toggle">
|
|
522
|
+
Toggle theme
|
|
523
|
+
</button>
|
|
524
|
+
<script type="module">
|
|
525
|
+
import { brontoVegaConfig } from '@ponchia/ui/vega'; // http(s) origin only
|
|
526
|
+
const root = document.documentElement;
|
|
527
|
+
let view; // the previous Vega view, so we can tear it down before re-embedding
|
|
528
|
+
const renderChart = async () => {
|
|
529
|
+
const theme = root.dataset.theme === 'dark' ? 'dark' : 'light';
|
|
530
|
+
const host = document.querySelector('#focus-chart');
|
|
531
|
+
view?.finalize(); // 1. finalize the old view first — frees its listeners/RAF (see below)
|
|
532
|
+
host.replaceChildren(); // 2. clear the host — re-embedding into a non-empty node stacks SVGs
|
|
533
|
+
const res = await vegaEmbed(host, spec, {
|
|
534
|
+
config: brontoVegaConfig(theme),
|
|
535
|
+
renderer: 'svg',
|
|
536
|
+
actions: false,
|
|
537
|
+
});
|
|
538
|
+
view = res.view;
|
|
539
|
+
};
|
|
540
|
+
document.querySelector('#theme-toggle').addEventListener('click', () => {
|
|
541
|
+
root.dataset.theme = root.dataset.theme === 'dark' ? 'light' : 'dark';
|
|
542
|
+
renderChart(); // 2. re-render on every flip — the baked SVG does not follow CSS
|
|
543
|
+
});
|
|
544
|
+
renderChart();
|
|
545
|
+
</script>
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
Four foot-guns, each verified while dogfooding:
|
|
549
|
+
|
|
550
|
+
- **Finalize the previous view before re-embedding.** `vegaEmbed` resolves to
|
|
551
|
+
`{ view }`; a Vega view registers event listeners and an animation frame loop
|
|
552
|
+
that `replaceChildren()` does **not** unwind. Call `view.finalize()` on the
|
|
553
|
+
prior view before each re-render (as above), or a report that toggles theme
|
|
554
|
+
repeatedly leaks a view per toggle.
|
|
555
|
+
- **Clear the host before re-embedding.** vega-embed appends; embedding twice
|
|
556
|
+
into the same node stacks a second chart under the first. `replaceChildren()`
|
|
557
|
+
(or `host.innerHTML = ''`) first.
|
|
558
|
+
- **Avoid `width: 'container'` if the chart can be re-rendered while hidden.** A
|
|
559
|
+
container-sized Vega chart measures its parent at embed time; if that parent is
|
|
560
|
+
`display: none` (a collapsed section, an inactive tab) it measures `0` and
|
|
561
|
+
renders empty. Give the spec an explicit `width`, or re-embed when the section
|
|
562
|
+
becomes visible.
|
|
563
|
+
- **Mermaid: don't `innerHTML = svg` over the source.** `mermaid.render()`
|
|
564
|
+
returns the SVG string — write it to a *separate* mount, not back into the
|
|
565
|
+
`<pre class="mermaid">` that still holds the diagram source, or a re-theme has
|
|
566
|
+
nothing to re-render from. Keep the source and the rendered output in different
|
|
567
|
+
nodes.
|
|
568
|
+
|
|
569
|
+
For a report you will only ever **print/PDF**, skip all of this: render once in
|
|
570
|
+
the target theme, or use the frozen inline `<svg>` route, which has no runtime.
|
|
571
|
+
|
|
249
572
|
## Common templates
|
|
250
573
|
|
|
251
574
|
- Executive brief: compact cover, one summary block, KPI `ui-statgrid`, short
|
|
@@ -259,8 +582,9 @@ style itself.
|
|
|
259
582
|
|
|
260
583
|
## Print and PDF
|
|
261
584
|
|
|
262
|
-
The supported export target is modern Chromium print/PDF.
|
|
263
|
-
the
|
|
585
|
+
The supported export target is modern Chromium print/PDF. A bronto report is
|
|
586
|
+
static and zero-JS, so producing the PDF is just _load → print_ — you do not
|
|
587
|
+
need a full automatable browser, only a Chromium-class layout+print pass.
|
|
264
588
|
|
|
265
589
|
- **By hand:** open the report in Chrome/Edge → Print (Cmd/Ctrl+P) → "Save as
|
|
266
590
|
PDF". In **More settings**, enable **Background graphics** (the dialog's
|
|
@@ -268,13 +592,45 @@ the file:
|
|
|
268
592
|
out), and pick the paper size there. Paper size is a browser print setting,
|
|
269
593
|
not a token; the layer only themes the page _margin_ via
|
|
270
594
|
`--report-page-margin`.
|
|
271
|
-
- **Headless
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
595
|
+
- **Headless, lightweight:** use **`chrome-headless-shell`** — the minimal
|
|
596
|
+
headless-Chromium binary built for exactly this, a fraction of a full
|
|
597
|
+
browser's weight. Drive it through Playwright/Puppeteer (or raw CDP) and
|
|
598
|
+
always pass `printBackground: true`, or chart fills and legend swatches drop
|
|
599
|
+
out:
|
|
600
|
+
|
|
601
|
+
```js
|
|
602
|
+
import { chromium } from 'playwright'; // or puppeteer
|
|
603
|
+
const browser = await chromium.launch({ channel: 'chromium-headless-shell' });
|
|
604
|
+
const page = await browser.newPage();
|
|
605
|
+
await page.goto('file:///abs/path/report.html', { waitUntil: 'networkidle' });
|
|
606
|
+
await page.pdf({ path: 'report.pdf', format: 'A4', printBackground: true });
|
|
607
|
+
await browser.close();
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Install the binary with `npx playwright install chromium-headless-shell`
|
|
611
|
+
(Puppeteer ships its own). The repo's `scripts/render-pdf.mjs` is a working
|
|
612
|
+
copy of this (`npm run report:pdf -- report.html`); it is a dev/example
|
|
613
|
+
helper, not part of the published API — bronto does not own rendering.
|
|
614
|
+
- **As a service / from another language:** run Chromium-as-a-service
|
|
615
|
+
(e.g. **Gotenberg**'s `POST /forms/chromium/convert/html`, or a hosted CDP
|
|
616
|
+
endpoint) and POST the HTML + the `dist/css/*` assets. A Python/Go/any host
|
|
617
|
+
then needs no local browser. This is the natural fit for reports generated
|
|
618
|
+
by an LLM or service in another system.
|
|
619
|
+
|
|
620
|
+
The report prints ink-on-white regardless of the on-screen theme. The chart
|
|
621
|
+
fills and swatches carry `print-color-adjust: exact`, but the engine still
|
|
622
|
+
needs background printing enabled (`printBackground: true` headless, or
|
|
623
|
+
"Background graphics" by hand). The bare `chrome --headless --print-to-pdf`
|
|
624
|
+
CLI flag does **not** print backgrounds — use the scripted CDP/`page.pdf()`
|
|
625
|
+
path above for any report with charts.
|
|
626
|
+
|
|
627
|
+
A browserless engine (WeasyPrint, Prince, …) can work for text-and-table
|
|
628
|
+
reports if you feed it the **unlayered** CSS (`@ponchia/ui/css/unlayered/*` —
|
|
629
|
+
no `@layer`) and resolve colours from `tokens/resolved.json`; but `:has()` and
|
|
630
|
+
modern paged-media are not universally supported, so charts and edge cases may
|
|
631
|
+
degrade. For faithful output, stay on a Chromium-class engine. Older
|
|
275
632
|
HTML-to-PDF engines are not part of the browser floor and may not support
|
|
276
|
-
cascade layers, `oklch()`, `color-mix()`, `:has()`, or modern paged-media
|
|
277
|
-
behavior.
|
|
633
|
+
cascade layers, `oklch()`, `color-mix()`, `:has()`, or modern paged-media.
|
|
278
634
|
|
|
279
635
|
- Use `ui-print-only` for content that should appear only in print.
|
|
280
636
|
- Use `ui-screen-only` for navigation or helper content that should not print.
|
|
@@ -296,7 +652,10 @@ fake page numbers with inert markup.
|
|
|
296
652
|
|
|
297
653
|
Before returning a report, an LLM should verify:
|
|
298
654
|
|
|
299
|
-
- All `ui-*` classes exist in `@ponchia/ui/classes
|
|
655
|
+
- All `ui-*` classes exist in `@ponchia/ui/classes` (or `classes/classes.json`).
|
|
656
|
+
The `is-*` state hooks (`is-num`/`is-pos`/`is-neg`/`is-key` in `.ui-table`
|
|
657
|
+
cells and `.ui-stat` deltas, `is-open`/`is-active`) are valid but live
|
|
658
|
+
outside `cls` by design — keep them.
|
|
300
659
|
- The document has one `h1`, ordered headings, and a single main report region.
|
|
301
660
|
- Tables have captions and header cells.
|
|
302
661
|
- Charts have captions, direct labels or legends, and fallback data.
|
package/docs/sidenote.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Sidenote
|
|
2
|
+
|
|
3
|
+
`@ponchia/ui/css/sidenote.css` is an opt-in **margin-note** grammar in the Tufte
|
|
4
|
+
tradition — the channel for evidence, caveats, and provenance asides that belong
|
|
5
|
+
*beside* the prose, not interrupting it. `ui-sidenote` is numbered;
|
|
6
|
+
`ui-marginnote` is the unnumbered variant.
|
|
7
|
+
|
|
8
|
+
```css
|
|
9
|
+
@import '@ponchia/ui';
|
|
10
|
+
@import '@ponchia/ui/css/sidenote.css';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## How it behaves
|
|
14
|
+
|
|
15
|
+
- **Wide viewports** (≥ 60rem) float the note into the inline-end margin.
|
|
16
|
+
- **Narrow viewports** collapse it to an indented inline block right after its
|
|
17
|
+
anchor, so nothing is lost on a phone or in a narrow column.
|
|
18
|
+
- **Numbering** is a CSS counter — no host bookkeeping.
|
|
19
|
+
|
|
20
|
+
## Wiring — two things the host owns
|
|
21
|
+
|
|
22
|
+
1. **Where numbering restarts.** Put `counter-reset: ui-sidenote` on the article
|
|
23
|
+
(or a section) so the count starts at 1 there.
|
|
24
|
+
2. **The margin gutter.** At the same breakpoint, give that container
|
|
25
|
+
`padding-inline-end: calc(var(--sidenote-width) + var(--sidenote-gap))` so the
|
|
26
|
+
floated note has room. `--sidenote-width` defaults to `12rem`.
|
|
27
|
+
|
|
28
|
+
```html
|
|
29
|
+
<article style="counter-reset: ui-sidenote">
|
|
30
|
+
<p>
|
|
31
|
+
The migration cut p95 latency by 38%<label class="ui-sidenote__ref"></label>.
|
|
32
|
+
<span class="ui-sidenote">
|
|
33
|
+
Measured over the first 24h after rollout; see the incident review §4.
|
|
34
|
+
</span>
|
|
35
|
+
The error budget held<span class="ui-marginnote">No paging events fired.</span>.
|
|
36
|
+
</p>
|
|
37
|
+
</article>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The `.ui-sidenote__ref` renders the superscript number and increments the
|
|
41
|
+
counter; place the `.ui-sidenote` immediately after it in the DOM so it reads
|
|
42
|
+
the same number.
|
|
43
|
+
|
|
44
|
+
## Class reference
|
|
45
|
+
|
|
46
|
+
| Class | Role |
|
|
47
|
+
| --- | --- |
|
|
48
|
+
| `.ui-sidenote` | A numbered margin note. |
|
|
49
|
+
| `.ui-marginnote` | An unnumbered margin note (no `__ref`). |
|
|
50
|
+
| `.ui-sidenote__ref` | The inline superscript anchor; increments + prints the number. |
|
|
51
|
+
|
|
52
|
+
| Custom property | On | Meaning |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| `--sidenote-width` | `.ui-sidenote` / `.ui-marginnote` | Floated width on wide viewports (default `12rem`). |
|
|
55
|
+
| `--sidenote-gap` | same | Gap between the text column and the note (default `2rem`). |
|
|
56
|
+
|
|
57
|
+
## Accessibility & robustness
|
|
58
|
+
|
|
59
|
+
- The note is **real DOM in reading order**, so a screen reader encounters it
|
|
60
|
+
right after the anchor — no off-screen trickery.
|
|
61
|
+
- On narrow viewports the note is always visible (an indented block with an
|
|
62
|
+
inline-start rule), so there is no hidden-content trap.
|
|
63
|
+
- Keep the note's meaning in its text; the number is a wayfinding aid, not the
|
|
64
|
+
content.
|
package/docs/sources.md
CHANGED
|
@@ -31,6 +31,29 @@ the only one.
|
|
|
31
31
|
| Stale | `ui-src--stale` | warning |
|
|
32
32
|
| Conflict | `ui-src--conflict` | danger |
|
|
33
33
|
|
|
34
|
+
A trust-state class **needs a host element** — on its own a tone class
|
|
35
|
+
(`ui-src--verified` and the rest) only sets a `--src-tone`, it does not draw
|
|
36
|
+
anything. The host is one of the elements below, or the standalone `.ui-src`
|
|
37
|
+
pill.
|
|
38
|
+
|
|
39
|
+
## Standalone trust pill — `.ui-src`
|
|
40
|
+
|
|
41
|
+
When you just need a bare labelled chip (a row of provenance tags, an inline
|
|
42
|
+
"verified" badge) with no surrounding card or citation, use `.ui-src` as the
|
|
43
|
+
host and add a tone. It draws a small pill with a leading trust dot:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<span class="ui-src ui-src--verified">Verified</span>
|
|
47
|
+
<span class="ui-src ui-src--generated">AI-generated</span>
|
|
48
|
+
<span class="ui-src ui-src--stale">Stale · 14d</span>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The base `.ui-src` is required — `<span class="ui-src--verified">` alone
|
|
52
|
+
**validates against `classes.json` but renders nothing**, because the tone class
|
|
53
|
+
only carries the colour for a host to consume. The word inside is the channel
|
|
54
|
+
(WCAG 1.4.1); the dot and tint are reinforcement. Reach for `.ui-citation--chip`
|
|
55
|
+
instead when the pill is a real link/button to the source.
|
|
56
|
+
|
|
34
57
|
## Inline citation — `.ui-citation`
|
|
35
58
|
|
|
36
59
|
A reference marker on a real `<a>` or `<button>` (the visual index is never the
|
|
@@ -64,6 +87,10 @@ A single source preview: title, origin, time, excerpt, actions. The
|
|
|
64
87
|
</article>
|
|
65
88
|
```
|
|
66
89
|
|
|
90
|
+
The parts are `__title`, `__origin`, `__time`, `__excerpt` (the body text — not
|
|
91
|
+
`__detail`/`__body`), and `__actions`. A card nests inside a
|
|
92
|
+
`ui-source-list__item` when it sits in a references section (below).
|
|
93
|
+
|
|
67
94
|
## Source list — `.ui-source-list`
|
|
68
95
|
|
|
69
96
|
A references section: a reset list of source cards (or rows).
|