@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.
Files changed (196) hide show
  1. package/CHANGELOG.md +386 -4
  2. package/MIGRATIONS.json +14 -0
  3. package/README.md +29 -6
  4. package/annotations/index.d.ts +398 -276
  5. package/annotations/index.d.ts.map +1 -0
  6. package/annotations/index.js +350 -77
  7. package/behaviors/carousel.d.ts +28 -0
  8. package/behaviors/carousel.d.ts.map +1 -0
  9. package/behaviors/carousel.js +20 -16
  10. package/behaviors/combobox.d.ts +40 -0
  11. package/behaviors/combobox.d.ts.map +1 -0
  12. package/behaviors/combobox.js +111 -29
  13. package/behaviors/command.d.ts +41 -0
  14. package/behaviors/command.d.ts.map +1 -0
  15. package/behaviors/command.js +27 -15
  16. package/behaviors/connectors.d.ts +17 -0
  17. package/behaviors/connectors.d.ts.map +1 -0
  18. package/behaviors/connectors.js +7 -5
  19. package/behaviors/crosshair.d.ts +42 -0
  20. package/behaviors/crosshair.d.ts.map +1 -0
  21. package/behaviors/crosshair.js +23 -6
  22. package/behaviors/dialog.d.ts +20 -0
  23. package/behaviors/dialog.d.ts.map +1 -0
  24. package/behaviors/dialog.js +6 -2
  25. package/behaviors/disclosure.d.ts +10 -0
  26. package/behaviors/disclosure.d.ts.map +1 -0
  27. package/behaviors/disclosure.js +6 -2
  28. package/behaviors/dismissible.d.ts +10 -0
  29. package/behaviors/dismissible.d.ts.map +1 -0
  30. package/behaviors/dismissible.js +6 -2
  31. package/behaviors/forms.d.ts +27 -0
  32. package/behaviors/forms.d.ts.map +1 -0
  33. package/behaviors/forms.js +54 -13
  34. package/behaviors/glyph.d.ts +14 -0
  35. package/behaviors/glyph.d.ts.map +1 -0
  36. package/behaviors/glyph.js +28 -5
  37. package/behaviors/index.d.ts +31 -237
  38. package/behaviors/index.d.ts.map +1 -0
  39. package/behaviors/index.js +17 -0
  40. package/behaviors/inert.d.ts +20 -0
  41. package/behaviors/inert.d.ts.map +1 -0
  42. package/behaviors/inert.js +46 -0
  43. package/behaviors/internal.d.ts +25 -0
  44. package/behaviors/internal.d.ts.map +1 -0
  45. package/behaviors/internal.js +77 -1
  46. package/behaviors/legend.d.ts +35 -0
  47. package/behaviors/legend.d.ts.map +1 -0
  48. package/behaviors/legend.js +32 -2
  49. package/behaviors/menu.d.ts +16 -0
  50. package/behaviors/menu.d.ts.map +1 -0
  51. package/behaviors/menu.js +6 -2
  52. package/behaviors/modal.d.ts +41 -0
  53. package/behaviors/modal.d.ts.map +1 -0
  54. package/behaviors/modal.js +124 -0
  55. package/behaviors/popover.d.ts +28 -0
  56. package/behaviors/popover.d.ts.map +1 -0
  57. package/behaviors/popover.js +78 -7
  58. package/behaviors/spotlight.d.ts +17 -0
  59. package/behaviors/spotlight.d.ts.map +1 -0
  60. package/behaviors/spotlight.js +7 -5
  61. package/behaviors/table.d.ts +36 -0
  62. package/behaviors/table.d.ts.map +1 -0
  63. package/behaviors/table.js +84 -17
  64. package/behaviors/tabs.d.ts +20 -0
  65. package/behaviors/tabs.d.ts.map +1 -0
  66. package/behaviors/tabs.js +17 -14
  67. package/behaviors/theme.d.ts +54 -0
  68. package/behaviors/theme.d.ts.map +1 -0
  69. package/behaviors/theme.js +22 -3
  70. package/behaviors/toast.d.ts +49 -0
  71. package/behaviors/toast.d.ts.map +1 -0
  72. package/behaviors/toast.js +47 -3
  73. package/classes/classes.json +2527 -0
  74. package/classes/index.d.ts +134 -15
  75. package/classes/index.js +280 -80
  76. package/classes/vscode.css-custom-data.json +12 -0
  77. package/connectors/index.d.ts +201 -69
  78. package/connectors/index.d.ts.map +1 -0
  79. package/connectors/index.js +142 -25
  80. package/css/app.css +69 -13
  81. package/css/base.css +15 -10
  82. package/css/bullet.css +108 -0
  83. package/css/code.css +98 -0
  84. package/css/connectors.css +17 -0
  85. package/css/content.css +22 -3
  86. package/css/crosshair.css +7 -7
  87. package/css/dataviz.css +5 -1
  88. package/css/diff.css +153 -0
  89. package/css/disclosure.css +53 -7
  90. package/css/dots.css +94 -7
  91. package/css/feedback.css +97 -7
  92. package/css/forms.css +113 -4
  93. package/css/legend.css +16 -9
  94. package/css/marks.css +38 -8
  95. package/css/motion.css +98 -53
  96. package/css/navigation.css +7 -0
  97. package/css/overlay.css +90 -3
  98. package/css/primitives.css +158 -13
  99. package/css/report.css +73 -56
  100. package/css/sidenote.css +67 -0
  101. package/css/site.css +16 -2
  102. package/css/sources.css +43 -1
  103. package/css/spark.css +62 -0
  104. package/css/spotlight.css +1 -1
  105. package/css/table.css +9 -2
  106. package/css/term.css +110 -0
  107. package/css/textref.css +63 -0
  108. package/css/toc.css +91 -0
  109. package/css/tokens.css +49 -1
  110. package/css/tree.css +134 -0
  111. package/css/workbench.css +1 -1
  112. package/dist/bronto.css +1 -1
  113. package/dist/css/analytical.css +1 -1
  114. package/dist/css/app.css +1 -1
  115. package/dist/css/base.css +1 -1
  116. package/dist/css/bullet.css +1 -0
  117. package/dist/css/code.css +1 -0
  118. package/dist/css/connectors.css +1 -1
  119. package/dist/css/content.css +1 -1
  120. package/dist/css/crosshair.css +1 -1
  121. package/dist/css/diff.css +1 -0
  122. package/dist/css/disclosure.css +1 -1
  123. package/dist/css/dots.css +1 -1
  124. package/dist/css/feedback.css +1 -1
  125. package/dist/css/forms.css +1 -1
  126. package/dist/css/legend.css +1 -1
  127. package/dist/css/marks.css +1 -1
  128. package/dist/css/motion.css +1 -1
  129. package/dist/css/navigation.css +1 -1
  130. package/dist/css/overlay.css +1 -1
  131. package/dist/css/primitives.css +1 -1
  132. package/dist/css/report.css +1 -1
  133. package/dist/css/sidenote.css +1 -0
  134. package/dist/css/site.css +1 -1
  135. package/dist/css/sources.css +1 -1
  136. package/dist/css/spark.css +1 -0
  137. package/dist/css/spotlight.css +1 -1
  138. package/dist/css/table.css +1 -1
  139. package/dist/css/term.css +1 -0
  140. package/dist/css/textref.css +1 -0
  141. package/dist/css/toc.css +1 -0
  142. package/dist/css/tokens.css +1 -1
  143. package/dist/css/tree.css +1 -0
  144. package/dist/css/workbench.css +1 -1
  145. package/docs/adr/0003-theme-model.md +1 -1
  146. package/docs/annotations.md +133 -14
  147. package/docs/architecture.md +49 -6
  148. package/docs/bullet.md +78 -0
  149. package/docs/code.md +76 -0
  150. package/docs/contrast.md +116 -92
  151. package/docs/d2.md +196 -0
  152. package/docs/diff.md +146 -0
  153. package/docs/legends.md +23 -3
  154. package/docs/marks.md +9 -2
  155. package/docs/mermaid.md +169 -0
  156. package/docs/reference.md +201 -26
  157. package/docs/reporting.md +416 -57
  158. package/docs/sidenote.md +64 -0
  159. package/docs/sources.md +27 -0
  160. package/docs/spark.md +78 -0
  161. package/docs/stability.md +10 -2
  162. package/docs/term.md +81 -0
  163. package/docs/textref.md +78 -0
  164. package/docs/theming.md +44 -5
  165. package/docs/toc.md +83 -0
  166. package/docs/tree.md +74 -0
  167. package/docs/usage.md +354 -16
  168. package/docs/vega.md +244 -0
  169. package/docs/workbench.md +7 -1
  170. package/glyphs/glyphs.js +13 -5
  171. package/llms.txt +285 -14
  172. package/package.json +95 -17
  173. package/qwik/index.d.ts +44 -59
  174. package/qwik/index.d.ts.map +1 -0
  175. package/qwik/index.js +65 -3
  176. package/react/index.d.ts +41 -61
  177. package/react/index.d.ts.map +1 -0
  178. package/react/index.js +63 -3
  179. package/solid/index.d.ts +68 -61
  180. package/solid/index.d.ts.map +1 -0
  181. package/solid/index.js +66 -3
  182. package/tokens/d2.d.ts +38 -0
  183. package/tokens/d2.js +71 -0
  184. package/tokens/d2.json +43 -0
  185. package/tokens/index.d.ts +5 -5
  186. package/tokens/index.js +15 -1
  187. package/tokens/index.json +9 -0
  188. package/tokens/mermaid.d.ts +23 -0
  189. package/tokens/mermaid.js +181 -0
  190. package/tokens/mermaid.json +163 -0
  191. package/tokens/resolved.json +45 -1
  192. package/tokens/skins.js +3 -2
  193. package/tokens/tokens.dtcg.json +26 -0
  194. package/tokens/vega.d.ts +34 -0
  195. package/tokens/vega.js +155 -0
  196. package/tokens/vega.json +179 -0
@@ -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;
@@ -87,10 +90,18 @@
87
90
  (island-safe). Inert until applied, so existing layouts/baselines
88
91
  are unaffected. */
89
92
  .ui-cq {
90
- container: var(--cq-name, bronto) / inline-size;
91
- }
92
-
93
- @container bronto (max-width: 34rem) {
93
+ /* The container-name is fixed: the `@container bronto (…)` collapse queries
94
+ below hardcode `bronto`. There is no `--cq-name` knob — the name is not
95
+ author-tunable, and an author-set `--cq-name` is simply ignored (nothing
96
+ reads it). Name a different container yourself if you need a custom scope. */
97
+ container: bronto / inline-size;
98
+ }
99
+
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) {
94
105
  .ui-grid {
95
106
  --grid-min: 100%;
96
107
  }
@@ -164,7 +175,7 @@
164
175
  /* Inside an opt-in .ui-cq container, collapse to one column when the
165
176
  container (not the viewport) is narrow — keeps tiles usable in a slim
166
177
  panel. Inert without .ui-cq, so baselines are unaffected. */
167
- @container bronto (max-width: 30rem) {
178
+ @container bronto (max-inline-size: 30rem) {
168
179
  .ui-statgrid,
169
180
  .ui-app-metrics {
170
181
  grid-template-columns: 1fr;
@@ -178,6 +189,12 @@
178
189
  border-radius: var(--radius-md);
179
190
  display: grid;
180
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;
181
198
  padding: var(--space-md);
182
199
  }
183
200
 
@@ -196,8 +213,10 @@
196
213
  font-family: var(--display);
197
214
  font-size: 1.9rem;
198
215
  font-variant-numeric: tabular-nums;
199
- letter-spacing: 0.01em;
200
- line-height: 1;
216
+ font-weight: var(--display-weight-strong);
217
+ letter-spacing: 0.02em;
218
+ line-height: 1.05;
219
+ overflow-wrap: anywhere; /* break an unspaced ID/hash rather than overflow (audit C5) */
201
220
  }
202
221
 
203
222
  .ui-stat__delta,
@@ -216,12 +235,32 @@
216
235
  color: var(--danger);
217
236
  }
218
237
 
238
+ /* A direction arrow is the non-colour channel — colour alone (success/danger)
239
+ fails WCAG 1.4.1, so the tile carries the same ▲/▼ glyph as `.ui-delta`
240
+ (C13). Marked aria-hidden-equivalent by being generated content; the sign is
241
+ still in the author's text ("+12%"). */
242
+ .ui-stat__delta.is-pos::before,
243
+ .ui-app-metric__delta.is-pos::before {
244
+ content: '▲ ';
245
+ font-size: 0.85em;
246
+ }
247
+
248
+ .ui-stat__delta.is-neg::before,
249
+ .ui-app-metric__delta.is-neg::before {
250
+ content: '▼ ';
251
+ font-size: 0.85em;
252
+ }
253
+
219
254
  /* --- Numeric value vocabulary ---
220
255
  The same tabular/aligned/tone intent the table has shipped since
221
256
  0.1.0, but freed from `.ui-table` so cards, stats and inline figures
222
257
  share one P&L vocabulary. Token-identical to the table's is-num/
223
258
  is-pos/is-neg (which stay table-local). */
224
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;
225
264
  font-variant-numeric: tabular-nums;
226
265
  text-align: end;
227
266
  }
@@ -238,6 +277,58 @@
238
277
  color: var(--text-dim);
239
278
  }
240
279
 
280
+ /* --- Trend delta ---
281
+ A standalone change indicator: a direction arrow (the non-colour channel,
282
+ WCAG 1.4.1) plus the figure, tabular so a column of deltas aligns.
283
+ Direction sets the glyph AND the conventional tone (up = positive,
284
+ down = negative). When "up" is the bad direction (latency, error rate,
285
+ cost, churn), add `ui-delta--invert` to swap only the tone — the arrow
286
+ still reports real direction. Pair with words; the arrow is visual, not a
287
+ substitute for stating the change. */
288
+ .ui-delta {
289
+ align-items: baseline;
290
+ color: var(--text-soft);
291
+ display: inline-flex;
292
+ font-variant-numeric: tabular-nums;
293
+ gap: 0.2em;
294
+ }
295
+
296
+ .ui-delta::before {
297
+ font-size: 0.85em;
298
+ }
299
+
300
+ .ui-delta--up {
301
+ color: var(--success);
302
+ }
303
+
304
+ .ui-delta--up::before {
305
+ content: '▲';
306
+ }
307
+
308
+ .ui-delta--down {
309
+ color: var(--danger);
310
+ }
311
+
312
+ .ui-delta--down::before {
313
+ content: '▼';
314
+ }
315
+
316
+ .ui-delta--flat {
317
+ color: var(--text-dim);
318
+ }
319
+
320
+ .ui-delta--flat::before {
321
+ content: '—';
322
+ }
323
+
324
+ .ui-delta--invert.ui-delta--up {
325
+ color: var(--danger);
326
+ }
327
+
328
+ .ui-delta--invert.ui-delta--down {
329
+ color: var(--success);
330
+ }
331
+
241
332
  /* --- Eyebrow / section label — dot-matrix face --- */
242
333
 
243
334
  .ui-eyebrow {
@@ -338,6 +429,28 @@
338
429
  opacity: 0.45;
339
430
  }
340
431
 
432
+ /* aria-disabled keeps the element in the a11y tree but the browser does NOT
433
+ block activation (a real <a class="ui-button" aria-disabled> still navigates,
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.) */
441
+ .ui-button[aria-disabled='true'],
442
+ .ui-link[aria-disabled='true'] {
443
+ pointer-events: none;
444
+ }
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
+
341
454
  .ui-button:active {
342
455
  transform: translateY(1px);
343
456
  }
@@ -363,8 +476,13 @@
363
476
  }
364
477
 
365
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.) */
366
484
  .ui-button[aria-busy='true']::before {
367
- animation-duration: 1.4s;
485
+ border-block-start-color: currentcolor;
368
486
  }
369
487
  }
370
488
 
@@ -401,8 +519,8 @@
401
519
 
402
520
  .ui-link--arrow::after,
403
521
  .ui-link--cta::after {
404
- border-inline-end: 1px solid currentColor;
405
- border-block-start: 1px solid currentColor;
522
+ border-inline-end: 1px solid currentcolor;
523
+ border-block-start: 1px solid currentcolor;
406
524
  content: '';
407
525
  block-size: 0.42rem;
408
526
  transform: rotate(45deg);
@@ -410,6 +528,27 @@
410
528
  inline-size: 0.42rem;
411
529
  }
412
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
+
413
552
  /* --- Chip / tag --- */
414
553
 
415
554
  .ui-chip {
@@ -459,6 +598,10 @@
459
598
  (the soft tint can't carry 4.5:1 small-bold tone-on-tone — WCAG). */
460
599
  .ui-badge--accent {
461
600
  background: var(--accent-soft);
601
+
602
+ /* Accent mixes 45% (vs 40% for the status tones below) on purpose: the brand
603
+ hue is lower-chroma here than the status hues, so it needs a touch more to
604
+ read at the same border weight. Intentional, not a copy-paste slip. (Q15.) */
462
605
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
463
606
  }
464
607
 
@@ -551,6 +694,8 @@
551
694
  color: var(--text);
552
695
  font-family: var(--mono);
553
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) */
554
699
  }
555
700
 
556
701
  /* --- Hover (pointer only) --- */
@@ -593,7 +738,7 @@
593
738
 
594
739
  [dir='rtl'] .ui-link--arrow:hover::after,
595
740
  [dir='rtl'] .ui-link--cta:hover::after {
596
- transform: translateX(-0.14rem) rotate(45deg);
741
+ transform: translateX(-0.14rem) rotate(-45deg);
597
742
  }
598
743
 
599
744
  .ui-chip--accent:hover {
package/css/report.css CHANGED
@@ -7,6 +7,9 @@
7
7
  ========================================================================== */
8
8
 
9
9
  .ui-report {
10
+ /* Declare override-surface tokens first so authors can set them. */
11
+ --report-width: 72rem;
12
+ --report-padding-block: var(--space-2xl);
10
13
  --report-gap: var(--space-lg);
11
14
  --report-measure: 74ch;
12
15
  --report-page-margin: 18mm;
@@ -14,9 +17,9 @@
14
17
  color: var(--text-soft);
15
18
  display: grid;
16
19
  gap: var(--report-gap);
17
- inline-size: min(100%, var(--report-width, 72rem));
20
+ inline-size: min(100%, var(--report-width));
18
21
  margin-inline: auto;
19
- padding: var(--report-padding-block, var(--space-2xl)) var(--space-md);
22
+ padding: var(--report-padding-block) var(--space-md);
20
23
  }
21
24
 
22
25
  .ui-report--compact {
@@ -42,7 +45,7 @@
42
45
  padding-block: var(--space-xl) var(--space-lg);
43
46
  }
44
47
 
45
- .ui-report__header {
48
+ .ui-report__head {
46
49
  align-items: end;
47
50
  border-block-end: 1px solid var(--line);
48
51
  display: flex;
@@ -56,6 +59,7 @@
56
59
  color: var(--text);
57
60
  font-family: var(--display);
58
61
  font-size: calc(var(--text-xl) * 1.35);
62
+ font-weight: var(--display-weight-strong);
59
63
  letter-spacing: 0;
60
64
  line-height: 1.05;
61
65
  margin: 0;
@@ -171,6 +175,7 @@
171
175
  color: var(--text);
172
176
  font-family: var(--display);
173
177
  font-size: var(--text-xl);
178
+ font-weight: var(--display-weight);
174
179
  letter-spacing: 0;
175
180
  line-height: 1.1;
176
181
  margin: 0;
@@ -195,8 +200,7 @@
195
200
  margin: 0;
196
201
  }
197
202
 
198
- .ui-report__caption,
199
- .ui-chart__caption {
203
+ .ui-report__caption {
200
204
  color: var(--text-dim);
201
205
  font-family: var(--mono);
202
206
  font-size: var(--text-2xs);
@@ -216,65 +220,88 @@
216
220
  padding-block-start: var(--space-md);
217
221
  }
218
222
 
219
- .ui-chart {
220
- border: 1px solid var(--line);
221
- border-radius: var(--radius-md);
223
+ /* --- Comparison layout ---
224
+ The report layer's "A vs B" / before-after grid — the side-by-side layout
225
+ reports lean on that the grammar was missing. Fluid-first: columns wrap to
226
+ a single stack on a narrow screen via auto-fit, so two panels never
227
+ overflow. `ui-compare__col` is one side, `ui-compare__head` labels it.
228
+ `ui-compare--2up` pins exactly two equal columns for a hard before/after
229
+ pairing (still collapsing to one column on a phone). */
230
+ .ui-compare {
222
231
  display: grid;
223
- gap: var(--space-sm);
224
- padding: var(--space-md);
232
+ gap: var(--space-md);
233
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
225
234
  }
226
235
 
227
- /* The chart's data key lives in the standalone, portable `.ui-legend`
228
- (css/legend.css), not here — import it beside this kit when a chart needs
229
- a legend. The chart owns only its plot + bars + fallback below. */
230
- .ui-chart__plot {
231
- display: grid;
232
- gap: var(--space-xs);
236
+ .ui-compare--2up {
237
+ grid-template-columns: repeat(2, minmax(0, 1fr));
233
238
  }
234
239
 
235
- .ui-chart__bar {
236
- --chart-value: 0%;
240
+ @media (max-width: 33rem) {
241
+ .ui-compare--2up {
242
+ grid-template-columns: 1fr;
243
+ }
244
+ }
237
245
 
246
+ .ui-compare__col {
247
+ align-content: start;
238
248
  display: grid;
239
- gap: 0.35rem;
249
+ gap: var(--space-sm);
240
250
  }
241
251
 
242
- /* An author `display` outranks the UA `[hidden]` rule, so re-assert it: a host
243
- toggling a series (e.g. from a `bronto:legend:toggle` event) expects
244
- `bar.hidden = true` to actually hide the bar. */
245
- .ui-chart__bar[hidden] {
246
- display: none;
252
+ .ui-compare__head {
253
+ color: var(--text-dim);
254
+ font-family: var(--mono);
255
+ font-size: var(--text-2xs);
256
+ letter-spacing: var(--tracking-wide);
257
+ text-transform: uppercase;
247
258
  }
248
259
 
249
- .ui-chart__label {
260
+ /* --- Labelled meter row ---
261
+ A multi-meter block (SLO burn, error budgets, capacity) lays out as
262
+ label | bar | value. The bare `ui-meter` base lives in feedback.css; this is
263
+ the report-document grammar around it so authors stop hand-rolling the grid.
264
+ The bar NEVER carries the reading alone (WCAG 1.4.1) — `ui-meter__value` is
265
+ the data of record, and the bar clamps at 100 so an over-target figure still
266
+ reads correctly in the value text. Collapses to a stack on a narrow screen. */
267
+ .ui-meter__row {
250
268
  align-items: center;
251
- color: var(--text-soft);
252
- display: flex;
253
- font-family: var(--mono);
254
- font-size: var(--text-xs);
255
- gap: var(--space-sm);
256
- justify-content: space-between;
269
+ display: grid;
270
+ gap: var(--space-2xs) var(--space-md);
271
+ grid-template-columns: minmax(9rem, 14rem) 1fr auto;
272
+ margin-block: var(--space-2xs);
257
273
  }
258
274
 
259
- .ui-chart__track {
260
- background: var(--panel-soft);
261
- border: 1px solid var(--line);
262
- block-size: 0.8rem;
263
- min-inline-size: 0;
275
+ .ui-meter__row .ui-meter {
276
+ min-inline-size: 8rem;
264
277
  }
265
278
 
266
- .ui-chart__fill {
267
- background: var(--chart-color, var(--chart-1, var(--accent)));
268
- background-image: var(--chart-pattern, var(--chart-pattern-1, none));
269
- background-size: var(--chart-pattern-size, 8px);
270
- block-size: 100%;
271
- inline-size: clamp(0%, var(--chart-value), 100%);
279
+ .ui-meter__label {
280
+ color: var(--text-soft);
272
281
  }
273
282
 
274
- .ui-chart__fallback {
275
- margin-block-start: var(--space-sm);
283
+ .ui-meter__value {
284
+ color: var(--text);
285
+ font-family: var(--mono);
286
+ font-variant-numeric: tabular-nums;
287
+ text-align: end;
276
288
  }
277
289
 
290
+ @media (max-width: 32rem) {
291
+ .ui-meter__row {
292
+ grid-template-columns: 1fr;
293
+ }
294
+
295
+ .ui-meter__value {
296
+ text-align: start;
297
+ }
298
+ }
299
+
300
+ /* A chart is NOT a bronto component — it needs scales + data binding, which the
301
+ analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
302
+ (docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
303
+ `.ui-report__figure` with a `.ui-report__caption` + a `.ui-legend` key. */
304
+
278
305
  .ui-print-only {
279
306
  display: none !important;
280
307
  }
@@ -313,27 +340,17 @@
313
340
  padding: 0;
314
341
  }
315
342
 
316
- /* Meaning-carrying chart fills must survive the print "economy" default
317
- (which drops backgrounds) without relying on a `.ui-print-exact`
318
- ancestor. The accent on `.ui-report__summary` is a border, so it already
319
- prints. (Legend swatches print-protect themselves in css/legend.css.) */
320
- .ui-chart__fill {
321
- -webkit-print-color-adjust: exact;
322
- print-color-adjust: exact;
323
- }
324
-
325
343
  .ui-report__cover {
326
344
  min-block-size: auto;
327
345
  }
328
346
 
329
347
  .ui-report__cover,
330
- .ui-report__header,
348
+ .ui-report__head,
331
349
  .ui-report__section,
332
350
  .ui-report__summary,
333
351
  .ui-report__finding,
334
352
  .ui-report__evidence,
335
- .ui-report__figure,
336
- .ui-chart {
353
+ .ui-report__figure {
337
354
  break-inside: avoid;
338
355
  }
339
356
 
@@ -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/site.css CHANGED
@@ -162,11 +162,11 @@
162
162
  transform var(--duration-fast) var(--ease-spring);
163
163
  }
164
164
 
165
- .ui-sitenav a[aria-current] {
165
+ .ui-sitenav a[aria-current]:not([aria-current='false']) {
166
166
  color: var(--accent-text);
167
167
  }
168
168
 
169
- .ui-sitenav a[aria-current]::before {
169
+ .ui-sitenav a[aria-current]:not([aria-current='false'])::before {
170
170
  opacity: 1;
171
171
  transform: scale(1);
172
172
  }
@@ -182,6 +182,13 @@
182
182
  }
183
183
  }
184
184
 
185
+ @media (pointer: coarse) {
186
+ .ui-sitenav a {
187
+ min-block-size: 2.9rem;
188
+ padding-inline: 0.9rem;
189
+ }
190
+ }
191
+
185
192
  /* --- Mobile menu — native <details>, no JS required --- */
186
193
 
187
194
  .ui-sitemenu {
@@ -247,6 +254,13 @@
247
254
  }
248
255
  }
249
256
 
257
+ @media (pointer: coarse) {
258
+ .ui-sitemenu > summary {
259
+ min-block-size: 2.9rem;
260
+ padding-inline: 0.9rem;
261
+ }
262
+ }
263
+
250
264
  /* --- Footer --- */
251
265
 
252
266
  .ui-sitefooter {
package/css/sources.css CHANGED
@@ -19,6 +19,37 @@
19
19
  --src-tone: var(--text-dim);
20
20
  }
21
21
 
22
+ /* Standalone trust pill — wears a `.ui-src--*` tone on its own, for a bare
23
+ "verified" / "stale" label outside a citation or source card (the tone setters
24
+ below only paint a `--src-tone` custom property, so without this host a lone
25
+ `.ui-src--verified` validates against classes.json yet renders nothing). Tone
26
+ is a leading dot + a tinted border AND the author's written label, never colour
27
+ alone (WCAG 1.4.1). Declared before the `--*` setters so their tone wins. */
28
+ .ui-src {
29
+ --src-tone: var(--text-dim);
30
+
31
+ align-items: center;
32
+ background: var(--panel-soft);
33
+ border: 1px solid color-mix(in srgb, var(--src-tone) 45%, var(--line));
34
+ border-radius: var(--radius-pill);
35
+ color: var(--text-soft);
36
+ display: inline-flex;
37
+ font-size: var(--text-2xs);
38
+ gap: 0.35rem;
39
+ padding: 0.08rem 0.55rem;
40
+ vertical-align: baseline;
41
+ }
42
+
43
+ .ui-src::before {
44
+ background: var(--src-tone);
45
+ border-radius: 50%;
46
+ block-size: 0.45rem;
47
+ content: '';
48
+ flex: none;
49
+ inline-size: 0.45rem;
50
+ print-color-adjust: exact;
51
+ }
52
+
22
53
  .ui-src--verified {
23
54
  --src-tone: var(--success);
24
55
  }
@@ -169,7 +200,8 @@
169
200
  1.4.1). Keep the marks visible as CanvasText rather than letting them vanish. */
170
201
  @media (forced-colors: active) {
171
202
  .ui-citation--chip::before,
172
- .ui-provenance__item::before {
203
+ .ui-provenance__item::before,
204
+ .ui-src::before {
173
205
  background: CanvasText;
174
206
  }
175
207
 
@@ -177,3 +209,13 @@
177
209
  border-inline-start-color: CanvasText;
178
210
  }
179
211
  }
212
+
213
+ /* Print: an inline citation marker points into the references list, so never
214
+ expand its href inline. base.css expands `.ui-prose a[href^="http"]` with a
215
+ trailing "(url)"; on a superscript marker that dumps a full URL mid-sentence.
216
+ !important + print-scoped because base's rule is more specific. */
217
+ @media print {
218
+ .ui-citation[href]::after {
219
+ content: none !important;
220
+ }
221
+ }