@ponchia/ui 0.6.0 → 0.6.4

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 (162) hide show
  1. package/CHANGELOG.md +82 -4
  2. package/README.md +1 -1
  3. package/annotations/index.d.ts.map +1 -1
  4. package/annotations/index.js +36 -33
  5. package/behaviors/carousel.d.ts +28 -0
  6. package/behaviors/carousel.d.ts.map +1 -0
  7. package/behaviors/carousel.js +3 -0
  8. package/behaviors/combobox.d.ts +40 -0
  9. package/behaviors/combobox.d.ts.map +1 -0
  10. package/behaviors/combobox.js +71 -20
  11. package/behaviors/command.d.ts +41 -0
  12. package/behaviors/command.d.ts.map +1 -0
  13. package/behaviors/command.js +9 -0
  14. package/behaviors/connectors.d.ts +17 -0
  15. package/behaviors/connectors.d.ts.map +1 -0
  16. package/behaviors/connectors.js +3 -0
  17. package/behaviors/crosshair.d.ts +42 -0
  18. package/behaviors/crosshair.d.ts.map +1 -0
  19. package/behaviors/crosshair.js +19 -1
  20. package/behaviors/dialog.d.ts +20 -0
  21. package/behaviors/dialog.d.ts.map +1 -0
  22. package/behaviors/dialog.js +3 -0
  23. package/behaviors/disclosure.d.ts +10 -0
  24. package/behaviors/disclosure.d.ts.map +1 -0
  25. package/behaviors/disclosure.js +3 -0
  26. package/behaviors/dismissible.d.ts +10 -0
  27. package/behaviors/dismissible.d.ts.map +1 -0
  28. package/behaviors/dismissible.js +3 -0
  29. package/behaviors/forms.d.ts +27 -0
  30. package/behaviors/forms.d.ts.map +1 -0
  31. package/behaviors/forms.js +18 -5
  32. package/behaviors/glyph.d.ts +21 -0
  33. package/behaviors/glyph.d.ts.map +1 -0
  34. package/behaviors/glyph.js +82 -4
  35. package/behaviors/index.d.ts +31 -237
  36. package/behaviors/index.d.ts.map +1 -0
  37. package/behaviors/index.js +17 -0
  38. package/behaviors/inert.d.ts +20 -0
  39. package/behaviors/inert.d.ts.map +1 -0
  40. package/behaviors/inert.js +46 -0
  41. package/behaviors/internal.d.ts +25 -0
  42. package/behaviors/internal.d.ts.map +1 -0
  43. package/behaviors/internal.js +30 -1
  44. package/behaviors/legend.d.ts +35 -0
  45. package/behaviors/legend.d.ts.map +1 -0
  46. package/behaviors/legend.js +9 -0
  47. package/behaviors/menu.d.ts +16 -0
  48. package/behaviors/menu.d.ts.map +1 -0
  49. package/behaviors/menu.js +3 -0
  50. package/behaviors/modal.d.ts +41 -0
  51. package/behaviors/modal.d.ts.map +1 -0
  52. package/behaviors/modal.js +124 -0
  53. package/behaviors/popover.d.ts +28 -0
  54. package/behaviors/popover.d.ts.map +1 -0
  55. package/behaviors/popover.js +17 -17
  56. package/behaviors/spotlight.d.ts +17 -0
  57. package/behaviors/spotlight.d.ts.map +1 -0
  58. package/behaviors/spotlight.js +3 -0
  59. package/behaviors/table.d.ts +36 -0
  60. package/behaviors/table.d.ts.map +1 -0
  61. package/behaviors/table.js +48 -8
  62. package/behaviors/tabs.d.ts +20 -0
  63. package/behaviors/tabs.d.ts.map +1 -0
  64. package/behaviors/tabs.js +3 -0
  65. package/behaviors/theme.d.ts +54 -0
  66. package/behaviors/theme.d.ts.map +1 -0
  67. package/behaviors/theme.js +17 -0
  68. package/behaviors/toast.d.ts +49 -0
  69. package/behaviors/toast.d.ts.map +1 -0
  70. package/behaviors/toast.js +34 -2
  71. package/classes/classes.json +747 -15
  72. package/classes/index.d.ts +118 -3
  73. package/classes/index.js +264 -66
  74. package/connectors/index.d.ts +12 -0
  75. package/connectors/index.d.ts.map +1 -1
  76. package/connectors/index.js +23 -2
  77. package/css/app.css +26 -0
  78. package/css/bullet.css +108 -0
  79. package/css/code.css +98 -0
  80. package/css/content.css +15 -2
  81. package/css/crosshair.css +7 -7
  82. package/css/diff.css +153 -0
  83. package/css/disclosure.css +18 -4
  84. package/css/dots.css +246 -9
  85. package/css/feedback.css +39 -7
  86. package/css/forms.css +71 -3
  87. package/css/legend.css +5 -2
  88. package/css/motion.css +79 -14
  89. package/css/overlay.css +59 -2
  90. package/css/primitives.css +67 -8
  91. package/css/report.css +43 -4
  92. package/css/sidenote.css +67 -0
  93. package/css/skins.css +9 -0
  94. package/css/spark.css +76 -0
  95. package/css/table.css +16 -3
  96. package/css/term.css +110 -0
  97. package/css/textref.css +63 -0
  98. package/css/toc.css +91 -0
  99. package/css/tokens.css +14 -1
  100. package/css/tree.css +134 -0
  101. package/dist/bronto.css +1 -1
  102. package/dist/css/analytical.css +1 -1
  103. package/dist/css/app.css +1 -1
  104. package/dist/css/bullet.css +1 -0
  105. package/dist/css/code.css +1 -0
  106. package/dist/css/content.css +1 -1
  107. package/dist/css/crosshair.css +1 -1
  108. package/dist/css/diff.css +1 -0
  109. package/dist/css/disclosure.css +1 -1
  110. package/dist/css/dots.css +1 -1
  111. package/dist/css/feedback.css +1 -1
  112. package/dist/css/forms.css +1 -1
  113. package/dist/css/legend.css +1 -1
  114. package/dist/css/motion.css +1 -1
  115. package/dist/css/overlay.css +1 -1
  116. package/dist/css/primitives.css +1 -1
  117. package/dist/css/report.css +1 -1
  118. package/dist/css/sidenote.css +1 -0
  119. package/dist/css/skins.css +1 -1
  120. package/dist/css/spark.css +1 -0
  121. package/dist/css/table.css +1 -1
  122. package/dist/css/term.css +1 -0
  123. package/dist/css/textref.css +1 -0
  124. package/dist/css/toc.css +1 -0
  125. package/dist/css/tokens.css +1 -1
  126. package/dist/css/tree.css +1 -0
  127. package/docs/annotations.md +39 -0
  128. package/docs/architecture.md +2 -3
  129. package/docs/bullet.md +78 -0
  130. package/docs/code.md +76 -0
  131. package/docs/d2.md +4 -3
  132. package/docs/diff.md +146 -0
  133. package/docs/dots.md +146 -0
  134. package/docs/glyphs.md +114 -0
  135. package/docs/legends.md +8 -4
  136. package/docs/mermaid.md +21 -4
  137. package/docs/reference.md +168 -8
  138. package/docs/reporting.md +49 -17
  139. package/docs/sidenote.md +64 -0
  140. package/docs/spark.md +78 -0
  141. package/docs/stability.md +1 -0
  142. package/docs/term.md +81 -0
  143. package/docs/textref.md +78 -0
  144. package/docs/theming.md +44 -5
  145. package/docs/toc.md +83 -0
  146. package/docs/tree.md +74 -0
  147. package/docs/usage.md +264 -23
  148. package/docs/vega.md +22 -3
  149. package/glyphs/glyphs.d.ts +61 -0
  150. package/glyphs/glyphs.js +600 -31
  151. package/llms.txt +169 -15
  152. package/package.json +51 -7
  153. package/qwik/index.d.ts +4 -2
  154. package/qwik/index.d.ts.map +1 -1
  155. package/qwik/index.js +10 -0
  156. package/react/index.d.ts +4 -2
  157. package/react/index.d.ts.map +1 -1
  158. package/react/index.js +6 -0
  159. package/solid/index.d.ts +6 -2
  160. package/solid/index.d.ts.map +1 -1
  161. package/solid/index.js +6 -0
  162. package/tokens/skins.js +22 -9
package/css/overlay.css CHANGED
@@ -78,14 +78,25 @@ dialog.ui-modal[open]::backdrop {
78
78
 
79
79
  /* Controlled (non-<dialog>) usage. A portal/React modal that can't be a
80
80
  native <dialog> wears the same skin + open layout via `.is-open`.
81
- Backdrop, top-layer stacking and focus-trapping are then the
82
- consumer's responsibility (the native <dialog> path gets them free). */
81
+ Backdrop and top-layer stacking are then the consumer's responsibility
82
+ (the native <dialog> path gets them free); focus-trapping no longer is —
83
+ mark the overlay `data-bronto-modal` and run `initModal` for an
84
+ inert-based trap + focus-return + Escape signal. */
83
85
  .ui-modal.is-open {
84
86
  animation: uiToastIn var(--duration-base) var(--ease-spring) both;
85
87
  display: grid;
86
88
  grid-template-rows: auto 1fr auto;
87
89
  }
88
90
 
91
+ /* Background scroll-lock. A native <dialog> top layer does NOT stop the page
92
+ behind it scrolling, and the no-JS native path can't lock it in script — so
93
+ lock it in CSS, for both the native [open] dialog and the controlled .is-open
94
+ overlay (incl. the drawer modifier, same base). (component audit C13.) */
95
+ html:has(dialog.ui-modal[open]),
96
+ html:has(.ui-modal.is-open) {
97
+ overflow: hidden;
98
+ }
99
+
89
100
  .ui-modal__head {
90
101
  align-items: flex-start;
91
102
  border-block-end: 1px solid var(--line);
@@ -150,6 +161,21 @@ dialog.ui-modal[open]::backdrop {
150
161
  }
151
162
  }
152
163
 
164
+ /* Comfortable hit target on coarse pointers — this bespoke close button measured
165
+ ~26px on touch, below the 2.9rem floor the rest of the button family meets
166
+ (WCAG 2.5.8). Scoped to coarse so the fine-pointer (mouse) rendering, which
167
+ already clears 24×24, is unchanged; centre the glyph in the enlarged box.
168
+ (audit C3.) */
169
+ @media (pointer: coarse) {
170
+ .ui-modal__close {
171
+ align-items: center;
172
+ display: inline-flex;
173
+ justify-content: center;
174
+ min-block-size: 2.9rem;
175
+ min-inline-size: 2.9rem;
176
+ }
177
+ }
178
+
153
179
  /* --- Lightbox — a full-viewport <dialog> wrapping a .ui-carousel. The
154
180
  native <dialog> brings the top layer, focus-trap and Escape; initDialog
155
181
  adds open (`data-bronto-open`) + focus-return; initCarousel drives the
@@ -284,6 +310,25 @@ dialog.ui-modal[open]::backdrop {
284
310
  opacity: 1;
285
311
  }
286
312
 
313
+ /* Menu hover/focus paints var(--bg-accent), which HCM strips, so the hovered row
314
+ is indistinguishable from its siblings (focus keeps the UA ring; hover has no
315
+ such fallback). Re-assert a system Highlight like the combobox/command rows,
316
+ for parity with those sibling surfaces. (component audit C22.) */
317
+ @media (forced-colors: active) {
318
+ .ui-menu__item:hover,
319
+ .ui-menu__item:focus-visible {
320
+ forced-color-adjust: none;
321
+ background: Highlight;
322
+ color: HighlightText;
323
+ }
324
+
325
+ .ui-menu__item:hover::before,
326
+ .ui-menu__item:focus-visible::before {
327
+ background: HighlightText;
328
+ opacity: 1;
329
+ }
330
+ }
331
+
287
332
  /* --- Combobox: an input with a filtered listbox popup (APG pattern,
288
333
  wired by initCombobox). Reuses the menu surface tokens. --- */
289
334
  .ui-combobox {
@@ -337,6 +382,18 @@ dialog.ui-modal[open]::backdrop {
337
382
  color: var(--text);
338
383
  }
339
384
 
385
+ /* The active option is tracked by aria-activedescendant — it is NEVER DOM-focused,
386
+ so it gets no UA focus ring under HCM, and its only cue (--bg-accent) is stripped.
387
+ Re-assert a system Highlight like the command palette does. (audit C2.) */
388
+ @media (forced-colors: active) {
389
+ .ui-combobox__option.is-active,
390
+ .ui-combobox__option[aria-selected='true'] {
391
+ forced-color-adjust: none;
392
+ background: Highlight;
393
+ color: HighlightText;
394
+ }
395
+ }
396
+
340
397
  .ui-combobox__empty {
341
398
  color: var(--text-dim);
342
399
  font-size: var(--text-2xs);
@@ -71,12 +71,15 @@
71
71
  padding-inline: var(--center-gutter, var(--space-md));
72
72
  }
73
73
 
74
- /* Intrinsic aspect-ratio box; the media child fills it. */
74
+ /* Intrinsic aspect-ratio box; the media child fills it. The contract is ONE
75
+ child (a single <img>/<video>/<iframe>). Scope the fill to :first-child rather
76
+ than every child: a second child would otherwise be forced to 100%/100% +
77
+ object-fit and stack on top, silently breaking the ratio. (audit C34.) */
75
78
  .ui-ratio {
76
79
  aspect-ratio: var(--ratio, 16 / 9);
77
80
  }
78
81
 
79
- .ui-ratio > * {
82
+ .ui-ratio > :first-child {
80
83
  block-size: 100%;
81
84
  inline-size: 100%;
82
85
  object-fit: cover;
@@ -94,7 +97,11 @@
94
97
  container: bronto / inline-size;
95
98
  }
96
99
 
97
- @container bronto (max-width: 34rem) {
100
+ /* Logical `max-inline-size`, not physical `max-width`: the container is typed
101
+ `inline-size`, so the inline axis is the one actually tracked — the logical
102
+ query matches it in any writing mode (a physical `width` query silently
103
+ misses in a vertical WM). (component audit C34.) */
104
+ @container bronto (max-inline-size: 34rem) {
98
105
  .ui-grid {
99
106
  --grid-min: 100%;
100
107
  }
@@ -168,7 +175,7 @@
168
175
  /* Inside an opt-in .ui-cq container, collapse to one column when the
169
176
  container (not the viewport) is narrow — keeps tiles usable in a slim
170
177
  panel. Inert without .ui-cq, so baselines are unaffected. */
171
- @container bronto (max-width: 30rem) {
178
+ @container bronto (max-inline-size: 30rem) {
172
179
  .ui-statgrid,
173
180
  .ui-app-metrics {
174
181
  grid-template-columns: 1fr;
@@ -182,6 +189,12 @@
182
189
  border-radius: var(--radius-md);
183
190
  display: grid;
184
191
  gap: 0.4rem;
192
+
193
+ /* These tiles hold IDs / hashes / big numbers — the unbreakable-token case is
194
+ the common one. A grid item defaults to min-inline-size:auto, so a long
195
+ value pushes the whole statgrid track wider; allow the tile to shrink so the
196
+ value can wrap instead (paired with overflow-wrap on __value). (audit C5.) */
197
+ min-inline-size: 0;
185
198
  padding: var(--space-md);
186
199
  }
187
200
 
@@ -203,6 +216,7 @@
203
216
  font-weight: var(--display-weight-strong);
204
217
  letter-spacing: 0.02em;
205
218
  line-height: 1.05;
219
+ overflow-wrap: anywhere; /* break an unspaced ID/hash rather than overflow (audit C5) */
206
220
  }
207
221
 
208
222
  .ui-stat__delta,
@@ -243,6 +257,10 @@
243
257
  share one P&L vocabulary. Token-identical to the table's is-num/
244
258
  is-pos/is-neg (which stay table-local). */
245
259
  .ui-num {
260
+ /* inline-block so `text-align: end` actually applies: on a bare inline element
261
+ it computes but never paints (the box is shrink-wrapped), so an author who
262
+ followed the docs to right-align an inline figure saw no effect. (audit C17.) */
263
+ display: inline-block;
246
264
  font-variant-numeric: tabular-nums;
247
265
  text-align: end;
248
266
  }
@@ -413,13 +431,26 @@
413
431
 
414
432
  /* aria-disabled keeps the element in the a11y tree but the browser does NOT
415
433
  block activation (a real <a class="ui-button" aria-disabled> still navigates,
416
- a <button> still fires). Looking dead while staying live is the defect — kill
417
- pointer activation. (a11y review C3; native :disabled already inert.) */
434
+ a <button> still fires). `pointer-events: none` kills POINTER activation only
435
+ a focused control still fires on Enter/Space, which CSS cannot intercept. So
436
+ this looks-dead state is NOT keyboard-inert: for that, prefer native
437
+ `<button disabled>`, run `initDisabledGuard()` (it intercepts Enter/Space on
438
+ aria-disabled controls), or add `tabindex="-1"` (and drop `href` on a link).
439
+ See docs/usage.md "Disabled vs aria-disabled". (a11y review C3 / audit C4;
440
+ native :disabled already inert.) */
418
441
  .ui-button[aria-disabled='true'],
419
442
  .ui-link[aria-disabled='true'] {
420
443
  pointer-events: none;
421
444
  }
422
445
 
446
+ /* The button family dims + shows not-allowed via its `:disabled` rule above, but
447
+ a disabled LINK got only pointer-events:none — it looked fully live. Give it
448
+ the same visual disabled cue. (component audit C30.) */
449
+ .ui-link[aria-disabled='true'] {
450
+ cursor: not-allowed;
451
+ opacity: 0.45;
452
+ }
453
+
423
454
  .ui-button:active {
424
455
  transform: translateY(1px);
425
456
  }
@@ -445,8 +476,13 @@
445
476
  }
446
477
 
447
478
  @media (prefers-reduced-motion: reduce) {
479
+ /* The global reduced-motion reset freezes the spin on a single frame with an
480
+ !important `animation-duration`, so the old non-important `1.4s` slow-spin
481
+ here was dead code AND left a broken, transparent-topped ring looking like a
482
+ rendering bug. Drop the dead rule; show a STATIC complete ring instead — a
483
+ still busy cue with no implied motion. (component audit C15.) */
448
484
  .ui-button[aria-busy='true']::before {
449
- animation-duration: 1.4s;
485
+ border-block-start-color: currentcolor;
450
486
  }
451
487
  }
452
488
 
@@ -492,6 +528,27 @@
492
528
  inline-size: 0.42rem;
493
529
  }
494
530
 
531
+ /* RTL: the logical borders flip sides, but a fixed `rotate(45deg)` then points
532
+ the chevron UP rather than toward the inline-end (the reading-forward way).
533
+ Mirror the rotation so the resting affordance points forward in RTL too.
534
+ (component audit C14.) */
535
+ [dir='rtl'] .ui-link--arrow::after,
536
+ [dir='rtl'] .ui-link--cta::after {
537
+ transform: rotate(-45deg);
538
+ }
539
+
540
+ /* Standalone CTA links are tap targets, not inline prose links: on a coarse
541
+ pointer float them to the WCAG 2.5.8 AA 24px floor (the 2.5.8 inline-link
542
+ exception doesn't cover a block-level call-to-action, which is what these
543
+ are). Buttons already auto-grow to ~44px on coarse pointers. (component
544
+ audit C14.) */
545
+ @media (pointer: coarse) {
546
+ .ui-link--arrow,
547
+ .ui-link--cta {
548
+ min-block-size: 1.5rem;
549
+ }
550
+ }
551
+
495
552
  /* --- Chip / tag --- */
496
553
 
497
554
  .ui-chip {
@@ -637,6 +694,8 @@
637
694
  color: var(--text);
638
695
  font-family: var(--mono);
639
696
  margin: 0;
697
+ min-inline-size: 0;
698
+ overflow-wrap: anywhere; /* IDs/hashes/paths are the common value — break, don't overflow (audit C5) */
640
699
  }
641
700
 
642
701
  /* --- Hover (pointer only) --- */
@@ -679,7 +738,7 @@
679
738
 
680
739
  [dir='rtl'] .ui-link--arrow:hover::after,
681
740
  [dir='rtl'] .ui-link--cta:hover::after {
682
- transform: translateX(-0.14rem) rotate(45deg);
741
+ transform: translateX(-0.14rem) rotate(-45deg);
683
742
  }
684
743
 
685
744
  .ui-chip--accent:hover {
package/css/report.css CHANGED
@@ -12,7 +12,6 @@
12
12
  --report-padding-block: var(--space-2xl);
13
13
  --report-gap: var(--space-lg);
14
14
  --report-measure: 74ch;
15
- --report-page-margin: 18mm;
16
15
 
17
16
  color: var(--text-soft);
18
17
  display: grid;
@@ -257,6 +256,46 @@
257
256
  text-transform: uppercase;
258
257
  }
259
258
 
259
+ /* --- Labelled meter row ---
260
+ A multi-meter block (SLO burn, error budgets, capacity) lays out as
261
+ label | bar | value. The bare `ui-meter` base lives in feedback.css; this is
262
+ the report-document grammar around it so authors stop hand-rolling the grid.
263
+ The bar NEVER carries the reading alone (WCAG 1.4.1) — `ui-meter__value` is
264
+ the data of record, and the bar clamps at 100 so an over-target figure still
265
+ reads correctly in the value text. Collapses to a stack on a narrow screen. */
266
+ .ui-meter__row {
267
+ align-items: center;
268
+ display: grid;
269
+ gap: var(--space-2xs) var(--space-md);
270
+ grid-template-columns: minmax(9rem, 14rem) 1fr auto;
271
+ margin-block: var(--space-2xs);
272
+ }
273
+
274
+ .ui-meter__row .ui-meter {
275
+ min-inline-size: 8rem;
276
+ }
277
+
278
+ .ui-meter__label {
279
+ color: var(--text-soft);
280
+ }
281
+
282
+ .ui-meter__value {
283
+ color: var(--text);
284
+ font-family: var(--mono);
285
+ font-variant-numeric: tabular-nums;
286
+ text-align: end;
287
+ }
288
+
289
+ @media (max-width: 32rem) {
290
+ .ui-meter__row {
291
+ grid-template-columns: 1fr;
292
+ }
293
+
294
+ .ui-meter__value {
295
+ text-align: start;
296
+ }
297
+ }
298
+
260
299
  /* A chart is NOT a bronto component — it needs scales + data binding, which the
261
300
  analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
262
301
  (docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
@@ -272,10 +311,10 @@
272
311
  }
273
312
 
274
313
  @media print {
275
- /* Re-tokenises the base 18mm default (base.css) so report margins stay
276
- themeable via --report-page-margin. */
314
+ /* Chromium-class print engines do not reliably resolve custom properties in
315
+ @page rules. Keep this literal so reports cannot print edge-to-edge. */
277
316
  @page {
278
- margin: var(--report-page-margin);
317
+ margin: 18mm;
279
318
  }
280
319
 
281
320
  .ui-report {
@@ -0,0 +1,67 @@
1
+ /* ==========================================================================
2
+ sidenote — opt-in Tufte-style margin notes.
3
+
4
+ A numbered `ui-sidenote` and an unnumbered `ui-marginnote` for evidence,
5
+ caveats, and provenance asides that belong beside the text, not in it. Wide
6
+ viewports float the note into the inline-end margin; narrow viewports collapse
7
+ it to an indented inline block. CSS counters number the sidenotes. Not
8
+ imported by core.css.
9
+
10
+ Boundary / wiring: the HOST owns where numbering restarts — set
11
+ `counter-reset: ui-sidenote` on the article (or a section) — and reserves the
12
+ margin gutter by giving that container
13
+ `padding-inline-end: calc(var(--sidenote-width) + var(--sidenote-gap))` at the
14
+ same breakpoint. Place each note in the DOM right after its `.ui-sidenote__ref`.
15
+ ========================================================================== */
16
+
17
+ .ui-sidenote,
18
+ .ui-marginnote {
19
+ --sidenote-width: 12rem;
20
+ --sidenote-gap: 2rem;
21
+
22
+ border-inline-start: 2px solid var(--line);
23
+ color: var(--text-dim);
24
+ display: block;
25
+ font-size: var(--text-2xs);
26
+ line-height: 1.5;
27
+ margin-block: var(--space-2xs);
28
+ padding-inline-start: var(--space-md);
29
+ }
30
+
31
+ /* The inline superscript that anchors a numbered sidenote. Use --accent-text,
32
+ not raw --accent: this is readable text and must clear WCAG AA 4.5:1 even
33
+ after a one-knob re-brand to a paler --accent (raw --accent drops to ~1.5:1).
34
+ Same accent-as-text contract as .ui-eyebrow / .ui-link--cta. (audit C6.) */
35
+ .ui-sidenote__ref {
36
+ color: var(--accent-text);
37
+ counter-increment: ui-sidenote;
38
+ font-size: 0.75em;
39
+ vertical-align: super;
40
+ }
41
+
42
+ .ui-sidenote__ref::after {
43
+ content: counter(ui-sidenote);
44
+ }
45
+
46
+ /* The note repeats its number (display only — the ref already incremented).
47
+ --accent-text for the same WCAG-AA reason as the ref above. (audit C6.) */
48
+ .ui-sidenote::before {
49
+ color: var(--accent-text);
50
+ content: counter(ui-sidenote) '. ';
51
+ }
52
+
53
+ /* Wide viewports: float the note into the inline-end margin. The container must
54
+ reserve the gutter (see header). */
55
+ @media (min-width: 60rem) {
56
+ .ui-sidenote,
57
+ .ui-marginnote {
58
+ border-inline-start: 0;
59
+ clear: inline-end;
60
+ float: inline-end;
61
+ inline-size: var(--sidenote-width);
62
+ margin-block: 0;
63
+ margin-inline-end: calc(-1 * (var(--sidenote-width) + var(--sidenote-gap)));
64
+ padding-inline-start: 0;
65
+ text-align: start;
66
+ }
67
+ }
package/css/skins.css CHANGED
@@ -11,44 +11,53 @@
11
11
  /* Amber CRT */
12
12
  :root[data-bronto-skin='amber-crt'] {
13
13
  --accent: oklch(52% 0.11 67deg);
14
+ --dotmatrix-pulse-min: 0.35;
14
15
  }
15
16
 
16
17
  :root[data-theme='dark'][data-bronto-skin='amber-crt'] {
17
18
  --accent: oklch(82% 0.15 82deg);
18
19
  --dotmatrix-glow: 0.4em;
20
+ --dotmatrix-pulse-min: 0.3;
19
21
  }
20
22
 
21
23
  /* E-ink */
22
24
  :root[data-bronto-skin='e-ink'] {
23
25
  --accent: oklch(34% 0.012 250deg);
26
+ --dotmatrix-reveal-step: 0ms;
24
27
  }
25
28
 
26
29
  :root[data-theme='dark'][data-bronto-skin='e-ink'] {
27
30
  --accent: oklch(84% 0.008 250deg);
31
+ --dotmatrix-reveal-step: 0ms;
28
32
  }
29
33
 
30
34
  /* Phosphor Green */
31
35
  :root[data-bronto-skin='phosphor-green'] {
32
36
  --accent: oklch(52% 0.13 150deg);
37
+ --dotmatrix-pulse-min: 0.35;
33
38
  }
34
39
 
35
40
  :root[data-theme='dark'][data-bronto-skin='phosphor-green'] {
36
41
  --accent: oklch(84% 0.19 150deg);
37
42
  --dotmatrix-glow: 0.4em;
43
+ --dotmatrix-pulse-min: 0.3;
38
44
  }
39
45
 
40
46
  @media (prefers-color-scheme: dark) {
41
47
  :root:not([data-theme='light'])[data-bronto-skin='amber-crt'] {
42
48
  --accent: oklch(82% 0.15 82deg);
43
49
  --dotmatrix-glow: 0.4em;
50
+ --dotmatrix-pulse-min: 0.3;
44
51
  }
45
52
 
46
53
  :root:not([data-theme='light'])[data-bronto-skin='e-ink'] {
47
54
  --accent: oklch(84% 0.008 250deg);
55
+ --dotmatrix-reveal-step: 0ms;
48
56
  }
49
57
 
50
58
  :root:not([data-theme='light'])[data-bronto-skin='phosphor-green'] {
51
59
  --accent: oklch(84% 0.19 150deg);
52
60
  --dotmatrix-glow: 0.4em;
61
+ --dotmatrix-pulse-min: 0.3;
53
62
  }
54
63
  }
package/css/spark.css ADDED
@@ -0,0 +1,76 @@
1
+ /* ==========================================================================
2
+ spark — opt-in inline datawords (word-sized microcharts).
3
+
4
+ A trend-in-a-sentence for generated reports and dense tables — the inline
5
+ counterpart to the scalar `ui-delta` / `ui-num` / `ui-stat`. Pure CSS, no
6
+ measurement, SSR-static, print-survivable. Not imported by core.css.
7
+
8
+ Boundary: the HOST normalises each point to 0..1 and sets it as `--v` on a
9
+ `.ui-spark__bar`; Bronto only paints the geometry. It refuses raw values and
10
+ min/max/scale computation. A bare spark is opaque to assistive tech, so the
11
+ container MUST carry a host-written `role="img"` + `aria-label` text
12
+ equivalent (e.g. "weekly signups, trending up"). Colour is never the only
13
+ channel — pair it with that label.
14
+ ========================================================================== */
15
+
16
+ .ui-spark {
17
+ align-items: flex-end;
18
+ block-size: 1em;
19
+ display: inline-flex;
20
+ gap: 1px;
21
+ inline-size: max-content;
22
+ vertical-align: -0.15em;
23
+ }
24
+
25
+ .ui-spark__bar {
26
+ background: currentColor;
27
+ block-size: max(1px, calc(var(--v, 0) * 100%));
28
+ border-radius: 0.5px;
29
+ flex: 0 0 0.25em;
30
+ min-inline-size: 2px;
31
+ }
32
+
33
+ /* Emphasise / tone a single bar — the rationed accent or a status tone. The
34
+ meaning still has to be in the aria-label (WCAG 1.4.1). */
35
+ .ui-spark__bar--accent {
36
+ background: var(--accent);
37
+ }
38
+
39
+ .ui-spark__bar--pos {
40
+ background: var(--success);
41
+ }
42
+
43
+ .ui-spark__bar--neg {
44
+ background: var(--danger);
45
+ }
46
+
47
+ /* Dot variant — each bar reads as a stack of dots (the dot-matrix dataword)
48
+ instead of a solid bar, for the signature look in a sentence/table. A vertical
49
+ repeating mask breaks every bar into segments; the host still only sets `--v`
50
+ per bar, so the contract is identical to the solid spark. */
51
+ .ui-spark--dots .ui-spark__bar {
52
+ --_dot: var(--spark-dot, 0.16em);
53
+ --_step: calc(var(--_dot) + var(--spark-dot-gap, 0.06em));
54
+
55
+ border-radius: 0;
56
+ /* stylelint-disable-next-line property-no-vendor-prefix -- Safari still needs the prefixed mask property. */
57
+ -webkit-mask: repeating-linear-gradient(to top, #000 0 var(--_dot), transparent var(--_dot) var(--_step));
58
+ mask: repeating-linear-gradient(to top, #000 0 var(--_dot), transparent var(--_dot) var(--_step));
59
+ }
60
+
61
+ /* Forced colours would force the bar backgrounds to the system surface and the
62
+ chart would vanish — repaint the bars in the system text colour so the shape
63
+ survives (the tone distinction is carried by the required aria-label). */
64
+ @media (forced-colors: active) {
65
+ .ui-spark__bar {
66
+ background: CanvasText;
67
+ }
68
+ }
69
+
70
+ /* Print: the bars are currentColor fills the print economy would drop. */
71
+ @media print {
72
+ .ui-spark__bar {
73
+ -webkit-print-color-adjust: exact;
74
+ print-color-adjust: exact;
75
+ }
76
+ }
package/css/table.css CHANGED
@@ -20,10 +20,11 @@
20
20
  .ui-table th,
21
21
  .ui-table td {
22
22
  border-block-end: 1px solid var(--line);
23
- overflow-wrap: anywhere;
23
+ overflow-wrap: break-word;
24
24
  padding: 0.7rem 0.85rem;
25
25
  text-align: start;
26
26
  vertical-align: top;
27
+ word-break: normal;
27
28
  }
28
29
 
29
30
  .ui-table th {
@@ -35,6 +36,11 @@
35
36
  position: sticky;
36
37
  text-transform: uppercase;
37
38
  inset-block-start: 0;
39
+
40
+ /* Keep the sticky header above body cells — cheap insurance for the
41
+ sticky-header + pinned/positioned-column combo, where an un-z-indexed th
42
+ scrolls under a positioned cell. (audit C30.) */
43
+ z-index: 1;
38
44
  }
39
45
 
40
46
  .ui-table td {
@@ -74,6 +80,11 @@
74
80
  border-inline-end: 0;
75
81
  }
76
82
 
83
+ .ui-table--break-anywhere th,
84
+ .ui-table--break-anywhere td {
85
+ overflow-wrap: anywhere;
86
+ }
87
+
77
88
  /* Numeric / right-aligned cells. */
78
89
  .ui-table .is-num,
79
90
  .ui-table th.is-num {
@@ -154,8 +165,10 @@
154
165
  padding: var(--space-sm) var(--space-md);
155
166
  }
156
167
 
157
- /* --- Loading state: set aria-busy + .ui-table--loading on the wrap --- */
158
- .ui-table--loading {
168
+ /* --- Loading state: set aria-busy + .ui-table-wrap--loading on the wrap. The
169
+ modifier is named for the element it goes ON (the wrap), not `.ui-table`, so
170
+ the BEM host matches the documented placement. (component audit C19.) --- */
171
+ .ui-table-wrap--loading {
159
172
  opacity: 0.6;
160
173
  pointer-events: none;
161
174
  }
package/css/term.css ADDED
@@ -0,0 +1,110 @@
1
+ /* ==========================================================================
2
+ term — opt-in inline glossary term + definition, and an end-of-report glossary.
3
+
4
+ The accessible upgrade of the famously touch/keyboard-broken `abbr[title]`: a
5
+ dotted-underline term whose definition lives in real, reachable DOM via the
6
+ native `[popover]` + `popovertarget` pairing, plus a `ui-glossary` `<dl>` block
7
+ that collects every term at the end of a document. Jargon that explains itself
8
+ inline and gathers into a reference — dead-centre on the explanation pillar.
9
+ Pure CSS over native popover, no kernel. Not imported by core.css.
10
+
11
+ Boundary: the HOST owns the wiring. The term is a `<button class="ui-term"
12
+ popovertarget="…">`; the definition is `<div class="ui-def" popover id="…">`.
13
+ That native pairing gives keyboard + touch + light-dismiss for free — no JS.
14
+ The glossary is a plain `<dl>`; the printed document leans on it because
15
+ popovers don't print (see docs/term.md). Anchor positioning is a gated
16
+ enhancement; without it the definition opens centred in the top layer.
17
+ ========================================================================== */
18
+
19
+ /* The inline term marker — a real <button> so it is keyboard- and touch-
20
+ reachable (the abbr[title] failure). Reset to inline text, keep a dotted
21
+ underline as the "has a definition" cue. */
22
+ .ui-term {
23
+ background: none;
24
+ border: 0;
25
+ color: inherit;
26
+ cursor: help;
27
+ font: inherit;
28
+ padding: 0;
29
+ text-decoration: underline;
30
+ text-decoration-color: var(--line-strong);
31
+ text-decoration-style: dotted;
32
+ text-underline-offset: 0.2em;
33
+ }
34
+
35
+ @media (hover: hover) {
36
+ .ui-term:hover {
37
+ text-decoration-color: var(--accent);
38
+ }
39
+ }
40
+
41
+ .ui-term:focus-visible {
42
+ outline: 2px solid var(--focus-ring);
43
+ outline-offset: 2px;
44
+ }
45
+
46
+ /* The definition — a native popover. Reset the UA popover chrome to the Bronto
47
+ raised-surface card. */
48
+ .ui-def {
49
+ background: var(--surface-raised);
50
+ border: 1px solid var(--line);
51
+ border-radius: var(--radius-md);
52
+ box-shadow: var(--shadow-raised);
53
+ color: var(--text);
54
+ font-family: var(--sans);
55
+ font-size: var(--text-sm);
56
+ line-height: 1.5;
57
+ margin: 0;
58
+ max-inline-size: 22rem;
59
+ padding: var(--space-sm) var(--space-md);
60
+ }
61
+
62
+ /* Progressive enhancement: anchor the definition to its term where CSS anchor
63
+ positioning exists, so it opens beside the word and flips at the viewport
64
+ edge. The host sets the matching `anchor-name` on the term (see docs). Without
65
+ support the popover keeps its centred top-layer fallback. */
66
+ @supports (anchor-name: --x) {
67
+ .ui-def {
68
+ inset: auto;
69
+ margin-block-start: 0.4rem;
70
+ position-area: block-end span-inline-end;
71
+ position-try-fallbacks: flip-block, flip-inline;
72
+ }
73
+ }
74
+
75
+ /* --- Glossary — the end-of-document <dl> the terms collect into. --- */
76
+
77
+ .ui-glossary {
78
+ border-block-start: 1px solid var(--line);
79
+ display: grid;
80
+ gap: var(--space-2xs) var(--space-md);
81
+ grid-template-columns: minmax(6rem, max-content) 1fr;
82
+ margin: 0;
83
+ padding-block-start: var(--space-md);
84
+ }
85
+
86
+ .ui-glossary__term {
87
+ color: var(--text);
88
+ font-family: var(--mono);
89
+ font-size: var(--text-xs);
90
+ font-weight: 600;
91
+ letter-spacing: var(--tracking-wide);
92
+ }
93
+
94
+ .ui-glossary__def {
95
+ color: var(--text-dim);
96
+ font-size: var(--text-sm);
97
+ margin: 0;
98
+ }
99
+
100
+ /* Narrow viewports: stack each term over its definition. */
101
+ @media (max-width: 32rem) {
102
+ .ui-glossary {
103
+ grid-template-columns: 1fr;
104
+ gap: 0.15rem var(--space-md);
105
+ }
106
+
107
+ .ui-glossary__def {
108
+ margin-block-end: var(--space-xs);
109
+ }
110
+ }