@ponchia/ui 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/CHANGELOG.md +552 -8
  2. package/MIGRATIONS.json +106 -0
  3. package/README.md +34 -8
  4. package/annotations/index.d.ts +402 -0
  5. package/annotations/index.d.ts.map +1 -0
  6. package/annotations/index.js +792 -0
  7. package/behaviors/carousel.js +198 -0
  8. package/behaviors/combobox.js +226 -0
  9. package/behaviors/command.js +190 -0
  10. package/behaviors/connectors.js +95 -0
  11. package/behaviors/crosshair.js +57 -0
  12. package/behaviors/dialog.js +74 -0
  13. package/behaviors/disclosure.js +26 -0
  14. package/behaviors/dismissible.js +25 -0
  15. package/behaviors/forms.js +186 -0
  16. package/behaviors/glyph.js +108 -0
  17. package/behaviors/index.d.ts +79 -0
  18. package/behaviors/index.js +18 -1409
  19. package/behaviors/internal.js +97 -0
  20. package/behaviors/legend.js +67 -0
  21. package/behaviors/menu.js +47 -0
  22. package/behaviors/popover.js +179 -0
  23. package/behaviors/spotlight.js +52 -0
  24. package/behaviors/table.js +136 -0
  25. package/behaviors/tabs.js +103 -0
  26. package/behaviors/theme.js +84 -0
  27. package/behaviors/toast.js +164 -0
  28. package/classes/classes.json +1857 -0
  29. package/classes/index.d.ts +306 -13
  30. package/classes/index.js +339 -12
  31. package/classes/vscode.css-custom-data.json +12 -0
  32. package/connectors/index.d.ts +191 -0
  33. package/connectors/index.d.ts.map +1 -0
  34. package/connectors/index.js +275 -0
  35. package/css/analytical.css +21 -0
  36. package/css/annotations.css +292 -0
  37. package/css/app.css +43 -13
  38. package/css/base.css +15 -10
  39. package/css/command.css +97 -0
  40. package/css/connectors.css +110 -0
  41. package/css/content.css +7 -1
  42. package/css/crosshair.css +100 -0
  43. package/css/dataviz.css +5 -1
  44. package/css/disclosure.css +38 -6
  45. package/css/dots.css +57 -0
  46. package/css/feedback.css +111 -2
  47. package/css/fonts.css +11 -7
  48. package/css/forms.css +42 -1
  49. package/css/generated.css +117 -0
  50. package/css/legend.css +272 -0
  51. package/css/marks.css +174 -0
  52. package/css/motion.css +24 -44
  53. package/css/navigation.css +7 -0
  54. package/css/overlay.css +31 -1
  55. package/css/primitives.css +109 -5
  56. package/css/report.css +39 -81
  57. package/css/selection.css +46 -0
  58. package/css/site.css +16 -2
  59. package/css/sources.css +221 -0
  60. package/css/spotlight.css +104 -0
  61. package/css/state.css +121 -0
  62. package/css/tokens.css +60 -37
  63. package/css/workbench.css +83 -0
  64. package/dist/bronto.css +1 -1
  65. package/dist/css/analytical.css +1 -0
  66. package/dist/css/annotations.css +1 -0
  67. package/dist/css/app.css +1 -1
  68. package/dist/css/base.css +1 -1
  69. package/dist/css/command.css +1 -0
  70. package/dist/css/connectors.css +1 -0
  71. package/dist/css/content.css +1 -1
  72. package/dist/css/crosshair.css +1 -0
  73. package/dist/css/disclosure.css +1 -1
  74. package/dist/css/dots.css +1 -1
  75. package/dist/css/feedback.css +1 -1
  76. package/dist/css/fonts.css +1 -1
  77. package/dist/css/forms.css +1 -1
  78. package/dist/css/generated.css +1 -0
  79. package/dist/css/legend.css +1 -0
  80. package/dist/css/marks.css +1 -0
  81. package/dist/css/motion.css +1 -1
  82. package/dist/css/navigation.css +1 -1
  83. package/dist/css/overlay.css +1 -1
  84. package/dist/css/primitives.css +1 -1
  85. package/dist/css/report.css +1 -1
  86. package/dist/css/selection.css +1 -0
  87. package/dist/css/site.css +1 -1
  88. package/dist/css/sources.css +1 -0
  89. package/dist/css/spotlight.css +1 -0
  90. package/dist/css/state.css +1 -0
  91. package/dist/css/tokens.css +1 -1
  92. package/dist/css/workbench.css +1 -0
  93. package/docs/adr/0003-theme-model.md +7 -4
  94. package/docs/annotations.md +425 -0
  95. package/docs/architecture.md +246 -0
  96. package/docs/command.md +95 -0
  97. package/docs/connectors.md +91 -0
  98. package/docs/contrast.md +116 -92
  99. package/docs/crosshair.md +63 -0
  100. package/docs/d2.md +195 -0
  101. package/docs/generated.md +91 -0
  102. package/docs/legends.md +184 -0
  103. package/docs/marks.md +93 -0
  104. package/docs/mermaid.md +152 -0
  105. package/docs/reference.md +385 -23
  106. package/docs/reporting.md +436 -63
  107. package/docs/selection.md +40 -0
  108. package/docs/sources.md +137 -0
  109. package/docs/spotlight.md +78 -0
  110. package/docs/stability.md +24 -2
  111. package/docs/state.md +85 -0
  112. package/docs/usage.md +123 -4
  113. package/docs/vega.md +225 -0
  114. package/docs/workbench.md +78 -0
  115. package/fonts/doto-400.woff2 +0 -0
  116. package/fonts/doto-500.woff2 +0 -0
  117. package/fonts/doto-600.woff2 +0 -0
  118. package/fonts/doto-700.woff2 +0 -0
  119. package/fonts/doto-800.woff2 +0 -0
  120. package/fonts/doto-900.woff2 +0 -0
  121. package/glyphs/glyphs.js +6 -4
  122. package/llms.txt +362 -14
  123. package/package.json +115 -12
  124. package/qwik/index.d.ts +42 -54
  125. package/qwik/index.d.ts.map +1 -0
  126. package/qwik/index.js +75 -3
  127. package/react/index.d.ts +39 -56
  128. package/react/index.d.ts.map +1 -0
  129. package/react/index.js +67 -3
  130. package/solid/index.d.ts +64 -56
  131. package/solid/index.d.ts.map +1 -0
  132. package/solid/index.js +70 -3
  133. package/tokens/d2.d.ts +38 -0
  134. package/tokens/d2.js +71 -0
  135. package/tokens/d2.json +43 -0
  136. package/tokens/index.d.ts +5 -5
  137. package/tokens/index.js +23 -5
  138. package/tokens/index.json +9 -0
  139. package/tokens/mermaid.d.ts +23 -0
  140. package/tokens/mermaid.js +181 -0
  141. package/tokens/mermaid.json +163 -0
  142. package/tokens/resolved.json +45 -1
  143. package/tokens/skins.js +3 -2
  144. package/tokens/tokens.dtcg.json +26 -0
  145. package/tokens/vega.d.ts +34 -0
  146. package/tokens/vega.js +155 -0
  147. package/tokens/vega.json +179 -0
  148. package/fonts/doto-400.ttf +0 -0
  149. package/fonts/doto-500.ttf +0 -0
  150. package/fonts/doto-600.ttf +0 -0
  151. package/fonts/doto-700.ttf +0 -0
  152. package/fonts/doto-800.ttf +0 -0
  153. package/fonts/doto-900.ttf +0 -0
package/css/marks.css ADDED
@@ -0,0 +1,174 @@
1
+ /* ==========================================================================
2
+ marks — opt-in evidence/emphasis marks for running text.
3
+
4
+ The prose counterpart to the SVG annotations layer: annotations call out a
5
+ figure, marks call out a sentence. Sober, report-grade highlights for docs,
6
+ audits, and generated (LLM) reports — not a hand-drawn Rough-Notation look.
7
+ Not imported by core.css.
8
+
9
+ `.ui-mark` is an inline highlight (put it on a `<mark>`); `.ui-bracket-note`
10
+ brackets a whole passage. Monochrome by default — the rationed accent is
11
+ opt-in via `--accent`; status tones are only for status-bearing emphasis.
12
+ ========================================================================== */
13
+
14
+ .ui-mark {
15
+ /* No base `--mark-color`: each draw style supplies its own fallback default
16
+ (highlight/box keep the subtle `--line-strong`; the full-opacity
17
+ `--underline`/`--strike` decorations default to the darker `--text-dim` so
18
+ they read on a light surface — C33). A tone modifier SETS `--mark-color`,
19
+ so it still wins everywhere the fallback would otherwise apply. */
20
+
21
+ /* A bare `<mark>` carries the UA `background-color: yellow`; the translucent
22
+ highlight gradient sits on top of it, so without this reset the UA yellow
23
+ bleeds through (an author declaration always beats the UA origin). */
24
+ background-color: transparent;
25
+ background-image: linear-gradient(
26
+ 90deg,
27
+ color-mix(in srgb, var(--mark-color, var(--line-strong)) 30%, transparent),
28
+ color-mix(in srgb, var(--mark-color, var(--line-strong)) 30%, transparent)
29
+ );
30
+ background-repeat: no-repeat;
31
+ background-size: 100% 100%;
32
+ border-radius: 0.12em;
33
+ box-decoration-break: clone;
34
+ color: inherit;
35
+ padding-block: 0.05em;
36
+ padding-inline: 0.18em;
37
+ }
38
+
39
+ /* Tones set the mark colour. Default is neutral ink; `--accent` is the
40
+ rationed accent; status tones only for status-bearing emphasis. */
41
+ .ui-mark--accent {
42
+ --mark-color: var(--accent);
43
+ }
44
+
45
+ .ui-mark--success {
46
+ --mark-color: var(--success);
47
+ }
48
+
49
+ .ui-mark--warning {
50
+ --mark-color: var(--warning);
51
+ }
52
+
53
+ .ui-mark--danger {
54
+ --mark-color: var(--danger);
55
+ }
56
+
57
+ .ui-mark--info {
58
+ --mark-color: var(--info);
59
+ }
60
+
61
+ .ui-mark--muted {
62
+ --mark-color: var(--text-dim);
63
+ }
64
+
65
+ /* Styles change how the mark is drawn (no highlight fill). They wrap across
66
+ lines natively (text-decoration) or with the cloned box (border). */
67
+ .ui-mark--underline {
68
+ background: none;
69
+ padding: 0;
70
+ text-decoration-color: var(--mark-color, var(--text-dim));
71
+ text-decoration-line: underline;
72
+ text-decoration-thickness: 0.12em;
73
+ text-underline-offset: 0.18em;
74
+ }
75
+
76
+ .ui-mark--box {
77
+ background: none;
78
+ border: 1px solid var(--mark-color, var(--line-strong));
79
+ border-radius: var(--radius-sm);
80
+ }
81
+
82
+ .ui-mark--strike {
83
+ background: none;
84
+ padding: 0;
85
+ text-decoration-color: var(--mark-color, var(--text-dim));
86
+ text-decoration-line: line-through;
87
+ }
88
+
89
+ /* Draw-on highlight sweep. Only the highlight fill animates, and only when
90
+ motion is welcome — reduced-motion keeps the resting full highlight. */
91
+ @media (prefers-reduced-motion: no-preference) {
92
+ /* The sweep animates `background-size`, i.e. the highlight FILL — so it is
93
+ inert on the no-fill styles (underline/box/strike). Scope it out rather
94
+ than let `--draw` look applied but do nothing. (content review C14.) */
95
+ .ui-mark--draw:not(.ui-mark--underline, .ui-mark--box, .ui-mark--strike) {
96
+ animation: ui-mark-draw 0.6s var(--ease, ease) both;
97
+ }
98
+ }
99
+
100
+ @keyframes ui-mark-draw {
101
+ from {
102
+ background-size: 0% 100%;
103
+ }
104
+
105
+ to {
106
+ background-size: 100% 100%;
107
+ }
108
+ }
109
+
110
+ /* A passage bracket: a tick down the inline-start with an optional label.
111
+ The prose analogue of `ui-annotation--bracket`. */
112
+ .ui-bracket-note {
113
+ --mark-color: var(--line-strong);
114
+
115
+ border-inline-start: 2px solid var(--mark-color);
116
+ display: block;
117
+ padding-inline-start: var(--space-md);
118
+ }
119
+
120
+ .ui-bracket-note__label {
121
+ color: var(--mark-color);
122
+ display: block;
123
+ font-family: var(--mono);
124
+ font-size: var(--text-2xs);
125
+ letter-spacing: 0;
126
+ margin-block-end: var(--space-2xs);
127
+ text-transform: uppercase;
128
+ }
129
+
130
+ .ui-bracket-note--accent {
131
+ --mark-color: var(--accent);
132
+ }
133
+
134
+ .ui-bracket-note--success {
135
+ --mark-color: var(--success);
136
+ }
137
+
138
+ .ui-bracket-note--warning {
139
+ --mark-color: var(--warning);
140
+ }
141
+
142
+ .ui-bracket-note--danger {
143
+ --mark-color: var(--danger);
144
+ }
145
+
146
+ .ui-bracket-note--info {
147
+ --mark-color: var(--info);
148
+ }
149
+
150
+ /* Forced colours: only the highlight `background` is dropped, so add an
151
+ underline to the plain highlight mark to keep it visible. The styled marks
152
+ already carry a surviving channel — `--underline`/`--strike` use
153
+ text-decoration and `--box` a border — so they're excluded (otherwise the
154
+ underline would overwrite `--strike`'s line-through and lose its meaning). */
155
+ @media (forced-colors: active) {
156
+ .ui-mark:not(.ui-mark--underline, .ui-mark--box, .ui-mark--strike) {
157
+ text-decoration-line: underline;
158
+ }
159
+ }
160
+
161
+ /* Print: the highlight is a painted background, so force it through the print
162
+ "economy" default that would otherwise drop it. Settle the draw sweep to its
163
+ resting full highlight in case it is mid-animation when the page is printed. */
164
+ @media print {
165
+ .ui-mark {
166
+ -webkit-print-color-adjust: exact;
167
+ background-size: 100% 100%;
168
+ print-color-adjust: exact;
169
+ }
170
+
171
+ .ui-mark--draw {
172
+ animation: none;
173
+ }
174
+ }
package/css/motion.css CHANGED
@@ -1,8 +1,6 @@
1
1
  /* ==========================================================================
2
2
  motion — keyframes + animation utilities
3
3
  Restrained, dot-flavoured. Everything collapses under reduced-motion.
4
- Keyframes from the legacy responsive layer live here so consumers that
5
- import only core.css keep their existing animations.
6
4
  ========================================================================== */
7
5
 
8
6
  @keyframes pulseDot {
@@ -18,45 +16,6 @@
18
16
  }
19
17
  }
20
18
 
21
- @keyframes scan {
22
- 0% {
23
- transform: translateY(-120%);
24
- }
25
-
26
- 100% {
27
- transform: translateY(320%);
28
- }
29
- }
30
-
31
- @keyframes growBar {
32
- to {
33
- transform: scaleX(1);
34
- }
35
- }
36
-
37
- @keyframes drawLine {
38
- from {
39
- opacity: 0;
40
- stroke-dasharray: 0 999;
41
- }
42
-
43
- to {
44
- opacity: 1;
45
- stroke-dasharray: 999 0;
46
- }
47
- }
48
-
49
- @keyframes pulseNode {
50
- 0%,
51
- 100% {
52
- transform: scale(1);
53
- }
54
-
55
- 50% {
56
- transform: scale(1.08);
57
- }
58
- }
59
-
60
19
  @keyframes pulseRing {
61
20
  0% {
62
21
  opacity: 0.7;
@@ -233,9 +192,13 @@
233
192
  animation-delay: 360ms;
234
193
  }
235
194
 
236
- /* Reveal-on-scroll: add .ui-reveal, toggle .is-visible via IntersectionObserver.
237
- Degrades to visible with no JS. */
238
- @media (prefers-reduced-motion: no-preference) {
195
+ /* Reveal-on-scroll: add .ui-reveal, then toggle .is-visible (e.g. from an
196
+ IntersectionObserver you own) to play it in. For a ZERO-JS reveal, use
197
+ `.ui-scroll-reveal` (scroll-driven, CSS-only) instead — that is the path for
198
+ an LLM-authored or no-build report. The hidden initial state below is gated
199
+ on `scripting: enabled`, so with scripting OFF the content is fully visible
200
+ and never silently hidden behind a script that will never run. */
201
+ @media (prefers-reduced-motion: no-preference) and (scripting: enabled) {
239
202
  .ui-reveal {
240
203
  opacity: 0;
241
204
  transform: translateY(14px);
@@ -365,14 +328,31 @@
365
328
  scroll-behavior: auto;
366
329
  }
367
330
 
331
+ /* !important is required: inside @layer bronto, layered rules lose to
332
+ unlayered author rules at equal specificity — only a layered !important
333
+ stays authoritative over unlayered declarations (CSS cascade §6.2). */
368
334
  *,
369
335
  *::before,
370
336
  *::after {
371
337
  animation-duration: 0.01ms !important;
372
338
  animation-iteration-count: 1 !important;
339
+
340
+ /* Zero the delay too: `.ui-stagger` children animate `uiRise` with
341
+ `fill-mode: both`, so a non-zero `animation-delay` holds them at the
342
+ `opacity: 0` from-state for the full delay and then pops them in — the
343
+ exact late flash a reduced-motion user asked to avoid (C4). */
344
+ animation-delay: 0s !important;
373
345
  transition-duration: 0.01ms !important;
374
346
  }
375
347
 
348
+ /* Freeze the shimmer gradient mid-sweep would expose a diagonal band.
349
+ Flatten the skeleton to its base panel colour instead. */
350
+ .ui-skeleton {
351
+ animation: none;
352
+ background: var(--panel-soft);
353
+ background-size: auto;
354
+ }
355
+
376
356
  .ui-reveal {
377
357
  opacity: 1 !important;
378
358
  transform: none !important;
@@ -80,3 +80,10 @@
80
80
  transform: translateY(1px);
81
81
  }
82
82
  }
83
+
84
+ @media (pointer: coarse) {
85
+ .ui-themetoggle__button {
86
+ min-block-size: 2.9rem;
87
+ padding-inline: 0.9rem;
88
+ }
89
+ }
package/css/overlay.css CHANGED
@@ -249,7 +249,7 @@ dialog.ui-modal[open]::backdrop {
249
249
  }
250
250
 
251
251
  .ui-menu__item::before {
252
- background: currentColor;
252
+ background: currentcolor;
253
253
  border-radius: 50%;
254
254
  content: '';
255
255
  block-size: 0.3rem;
@@ -275,6 +275,15 @@ dialog.ui-modal[open]::backdrop {
275
275
  }
276
276
  }
277
277
 
278
+ .ui-menu__item:focus-visible {
279
+ background: var(--bg-accent);
280
+ color: var(--text);
281
+ }
282
+
283
+ .ui-menu__item:focus-visible::before {
284
+ opacity: 1;
285
+ }
286
+
278
287
  /* --- Combobox: an input with a filtered listbox popup (APG pattern,
279
288
  wired by initCombobox). Reuses the menu surface tokens. --- */
280
289
  .ui-combobox {
@@ -363,6 +372,14 @@ dialog.ui-modal[open]::backdrop {
363
372
  inset-inline: 0;
364
373
  min-inline-size: 0;
365
374
  }
375
+
376
+ /* Let the combobox shed its 14rem floor and span the viewport so the input
377
+ (and its full-width listbox) can't overflow a narrow screen. */
378
+ .ui-combobox {
379
+ display: block;
380
+ inline-size: 100%;
381
+ min-inline-size: 0;
382
+ }
366
383
  }
367
384
 
368
385
  @media (prefers-reduced-motion: reduce) {
@@ -376,3 +393,16 @@ dialog.ui-modal[open]::backdrop {
376
393
  transition: none;
377
394
  }
378
395
  }
396
+
397
+ /* Forced-colors drops both `backdrop-filter: blur()` and the `color-mix()`
398
+ scrim, so the dialog/lightbox would float over an undimmed page with no
399
+ separation. Re-assert a translucent scrim with `forced-color-adjust: none`
400
+ so the layering reads in High Contrast, matching the meter/dot precedent
401
+ (C28). */
402
+ @media (forced-colors: active) {
403
+ .ui-modal::backdrop,
404
+ .ui-lightbox::backdrop {
405
+ background: color-mix(in srgb, #000 50%, transparent);
406
+ forced-color-adjust: none;
407
+ }
408
+ }
@@ -87,7 +87,11 @@
87
87
  (island-safe). Inert until applied, so existing layouts/baselines
88
88
  are unaffected. */
89
89
  .ui-cq {
90
- container: var(--cq-name, bronto) / inline-size;
90
+ /* The container-name is fixed: the `@container bronto (…)` collapse queries
91
+ below hardcode `bronto`. There is no `--cq-name` knob — the name is not
92
+ author-tunable, and an author-set `--cq-name` is simply ignored (nothing
93
+ reads it). Name a different container yourself if you need a custom scope. */
94
+ container: bronto / inline-size;
91
95
  }
92
96
 
93
97
  @container bronto (max-width: 34rem) {
@@ -196,8 +200,9 @@
196
200
  font-family: var(--display);
197
201
  font-size: 1.9rem;
198
202
  font-variant-numeric: tabular-nums;
199
- letter-spacing: 0.01em;
200
- line-height: 1;
203
+ font-weight: var(--display-weight-strong);
204
+ letter-spacing: 0.02em;
205
+ line-height: 1.05;
201
206
  }
202
207
 
203
208
  .ui-stat__delta,
@@ -216,6 +221,22 @@
216
221
  color: var(--danger);
217
222
  }
218
223
 
224
+ /* A direction arrow is the non-colour channel — colour alone (success/danger)
225
+ fails WCAG 1.4.1, so the tile carries the same ▲/▼ glyph as `.ui-delta`
226
+ (C13). Marked aria-hidden-equivalent by being generated content; the sign is
227
+ still in the author's text ("+12%"). */
228
+ .ui-stat__delta.is-pos::before,
229
+ .ui-app-metric__delta.is-pos::before {
230
+ content: '▲ ';
231
+ font-size: 0.85em;
232
+ }
233
+
234
+ .ui-stat__delta.is-neg::before,
235
+ .ui-app-metric__delta.is-neg::before {
236
+ content: '▼ ';
237
+ font-size: 0.85em;
238
+ }
239
+
219
240
  /* --- Numeric value vocabulary ---
220
241
  The same tabular/aligned/tone intent the table has shipped since
221
242
  0.1.0, but freed from `.ui-table` so cards, stats and inline figures
@@ -238,6 +259,58 @@
238
259
  color: var(--text-dim);
239
260
  }
240
261
 
262
+ /* --- Trend delta ---
263
+ A standalone change indicator: a direction arrow (the non-colour channel,
264
+ WCAG 1.4.1) plus the figure, tabular so a column of deltas aligns.
265
+ Direction sets the glyph AND the conventional tone (up = positive,
266
+ down = negative). When "up" is the bad direction (latency, error rate,
267
+ cost, churn), add `ui-delta--invert` to swap only the tone — the arrow
268
+ still reports real direction. Pair with words; the arrow is visual, not a
269
+ substitute for stating the change. */
270
+ .ui-delta {
271
+ align-items: baseline;
272
+ color: var(--text-soft);
273
+ display: inline-flex;
274
+ font-variant-numeric: tabular-nums;
275
+ gap: 0.2em;
276
+ }
277
+
278
+ .ui-delta::before {
279
+ font-size: 0.85em;
280
+ }
281
+
282
+ .ui-delta--up {
283
+ color: var(--success);
284
+ }
285
+
286
+ .ui-delta--up::before {
287
+ content: '▲';
288
+ }
289
+
290
+ .ui-delta--down {
291
+ color: var(--danger);
292
+ }
293
+
294
+ .ui-delta--down::before {
295
+ content: '▼';
296
+ }
297
+
298
+ .ui-delta--flat {
299
+ color: var(--text-dim);
300
+ }
301
+
302
+ .ui-delta--flat::before {
303
+ content: '—';
304
+ }
305
+
306
+ .ui-delta--invert.ui-delta--up {
307
+ color: var(--danger);
308
+ }
309
+
310
+ .ui-delta--invert.ui-delta--down {
311
+ color: var(--success);
312
+ }
313
+
241
314
  /* --- Eyebrow / section label — dot-matrix face --- */
242
315
 
243
316
  .ui-eyebrow {
@@ -338,6 +411,15 @@
338
411
  opacity: 0.45;
339
412
  }
340
413
 
414
+ /* aria-disabled keeps the element in the a11y tree but the browser does NOT
415
+ 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.) */
418
+ .ui-button[aria-disabled='true'],
419
+ .ui-link[aria-disabled='true'] {
420
+ pointer-events: none;
421
+ }
422
+
341
423
  .ui-button:active {
342
424
  transform: translateY(1px);
343
425
  }
@@ -401,8 +483,8 @@
401
483
 
402
484
  .ui-link--arrow::after,
403
485
  .ui-link--cta::after {
404
- border-inline-end: 1px solid currentColor;
405
- border-block-start: 1px solid currentColor;
486
+ border-inline-end: 1px solid currentcolor;
487
+ border-block-start: 1px solid currentcolor;
406
488
  content: '';
407
489
  block-size: 0.42rem;
408
490
  transform: rotate(45deg);
@@ -459,6 +541,10 @@
459
541
  (the soft tint can't carry 4.5:1 small-bold tone-on-tone — WCAG). */
460
542
  .ui-badge--accent {
461
543
  background: var(--accent-soft);
544
+
545
+ /* Accent mixes 45% (vs 40% for the status tones below) on purpose: the brand
546
+ hue is lower-chroma here than the status hues, so it needs a touch more to
547
+ read at the same border weight. Intentional, not a copy-paste slip. (Q15.) */
462
548
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
463
549
  }
464
550
 
@@ -649,6 +735,24 @@
649
735
  padding: 0.18rem 0.4rem;
650
736
  }
651
737
 
738
+ /* --- Shortcut hint — one or more .ui-kbd keys as a chord or sequence, with a
739
+ connective __sep ("+" for a chord, "then" for a sequence). The reusable
740
+ keyboard-hint primitive (menu items, buttons, tooltips, command lists). --- */
741
+
742
+ .ui-shortcut {
743
+ align-items: center;
744
+ display: inline-flex;
745
+ gap: 0.25rem;
746
+ vertical-align: middle;
747
+ white-space: nowrap;
748
+ }
749
+
750
+ .ui-shortcut__sep {
751
+ color: var(--text-dim);
752
+ font-family: var(--mono);
753
+ font-size: var(--text-2xs);
754
+ }
755
+
652
756
  /* --- Timeline — vertical event list on a hairline spine. Use an <ol>;
653
757
  each <li> is a __item with an optional __time label. aria-current marks
654
758
  the live/most-recent event (accent marker). --- */
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,82 +220,47 @@
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);
225
- }
226
-
227
- .ui-chart__legend {
228
- display: flex;
229
- flex-wrap: wrap;
230
- gap: var(--space-xs);
231
- list-style: none;
232
- margin: 0;
233
- padding: 0;
234
- }
235
-
236
- .ui-chart__legend > * {
237
- align-items: center;
238
- color: var(--text-soft);
239
- display: inline-flex;
240
- font-family: var(--mono);
241
- font-size: var(--text-xs);
242
- gap: 0.45rem;
232
+ gap: var(--space-md);
233
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
243
234
  }
244
235
 
245
- .ui-chart__swatch {
246
- background: var(--chart-color, var(--chart-1, var(--accent)));
247
- background-image: var(--chart-pattern, var(--chart-pattern-1, none));
248
- background-size: var(--chart-pattern-size, 8px);
249
- border: 1px solid var(--line-strong);
250
- block-size: 0.8rem;
251
- flex: 0 0 auto;
252
- inline-size: 0.8rem;
236
+ .ui-compare--2up {
237
+ grid-template-columns: repeat(2, minmax(0, 1fr));
253
238
  }
254
239
 
255
- .ui-chart__plot {
256
- display: grid;
257
- gap: var(--space-xs);
240
+ @media (max-width: 33rem) {
241
+ .ui-compare--2up {
242
+ grid-template-columns: 1fr;
243
+ }
258
244
  }
259
245
 
260
- .ui-chart__bar {
261
- --chart-value: 0%;
262
-
246
+ .ui-compare__col {
247
+ align-content: start;
263
248
  display: grid;
264
- gap: 0.35rem;
265
- }
266
-
267
- .ui-chart__label {
268
- align-items: center;
269
- color: var(--text-soft);
270
- display: flex;
271
- font-family: var(--mono);
272
- font-size: var(--text-xs);
273
249
  gap: var(--space-sm);
274
- justify-content: space-between;
275
250
  }
276
251
 
277
- .ui-chart__track {
278
- background: var(--panel-soft);
279
- border: 1px solid var(--line);
280
- block-size: 0.8rem;
281
- min-inline-size: 0;
282
- }
283
-
284
- .ui-chart__fill {
285
- background: var(--chart-color, var(--chart-1, var(--accent)));
286
- background-image: var(--chart-pattern, var(--chart-pattern-1, none));
287
- background-size: var(--chart-pattern-size, 8px);
288
- block-size: 100%;
289
- inline-size: clamp(0%, var(--chart-value), 100%);
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;
290
258
  }
291
259
 
292
- .ui-chart__fallback {
293
- margin-block-start: var(--space-sm);
294
- }
260
+ /* A chart is NOT a bronto component — it needs scales + data binding, which the
261
+ analytical layer refuses to own. Theme Vega-Lite with `@ponchia/ui/vega`
262
+ (docs/vega.md), or hand-author a token-themed inline `<svg>`, and drop it in a
263
+ `.ui-report__figure` with a `.ui-report__caption` + a `.ui-legend` key. */
295
264
 
296
265
  .ui-print-only {
297
266
  display: none !important;
@@ -331,28 +300,17 @@
331
300
  padding: 0;
332
301
  }
333
302
 
334
- /* Meaning-carrying chart fills must survive the print "economy" default
335
- (which drops backgrounds) without relying on a `.ui-print-exact`
336
- ancestor. The accent on `.ui-report__summary` is a border, so it already
337
- prints. */
338
- .ui-chart__fill,
339
- .ui-chart__swatch {
340
- -webkit-print-color-adjust: exact;
341
- print-color-adjust: exact;
342
- }
343
-
344
303
  .ui-report__cover {
345
304
  min-block-size: auto;
346
305
  }
347
306
 
348
307
  .ui-report__cover,
349
- .ui-report__header,
308
+ .ui-report__head,
350
309
  .ui-report__section,
351
310
  .ui-report__summary,
352
311
  .ui-report__finding,
353
312
  .ui-report__evidence,
354
- .ui-report__figure,
355
- .ui-chart {
313
+ .ui-report__figure {
356
314
  break-inside: avoid;
357
315
  }
358
316