@ponchia/ui 0.4.0 → 0.5.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 (123) hide show
  1. package/CHANGELOG.md +352 -12
  2. package/MIGRATIONS.json +92 -0
  3. package/README.md +27 -17
  4. package/annotations/index.d.ts +280 -0
  5. package/annotations/index.js +522 -0
  6. package/behaviors/carousel.js +197 -0
  7. package/behaviors/combobox.js +195 -0
  8. package/behaviors/command.js +187 -0
  9. package/behaviors/connectors.js +96 -0
  10. package/behaviors/crosshair.js +58 -0
  11. package/behaviors/dialog.js +73 -0
  12. package/behaviors/disclosure.js +25 -0
  13. package/behaviors/dismissible.js +24 -0
  14. package/behaviors/forms.js +158 -0
  15. package/behaviors/glyph.js +109 -0
  16. package/behaviors/index.d.ts +82 -1
  17. package/behaviors/index.js +18 -1336
  18. package/behaviors/internal.js +50 -0
  19. package/behaviors/legend.js +46 -0
  20. package/behaviors/menu.js +46 -0
  21. package/behaviors/popover.js +108 -0
  22. package/behaviors/spotlight.js +53 -0
  23. package/behaviors/table.js +109 -0
  24. package/behaviors/tabs.js +103 -0
  25. package/behaviors/theme.js +82 -0
  26. package/behaviors/toast.js +152 -0
  27. package/classes/index.d.ts +318 -0
  28. package/classes/index.js +352 -0
  29. package/classes/vscode.css-custom-data.json +18 -14
  30. package/connectors/index.d.ts +71 -0
  31. package/connectors/index.js +179 -0
  32. package/css/analytical.css +21 -0
  33. package/css/annotations.css +292 -0
  34. package/css/command.css +97 -0
  35. package/css/connectors.css +93 -0
  36. package/css/crosshair.css +100 -0
  37. package/css/disclosure.css +29 -0
  38. package/css/dots.css +2 -0
  39. package/css/feedback.css +104 -0
  40. package/css/fonts.css +11 -7
  41. package/css/generated.css +117 -0
  42. package/css/legend.css +268 -0
  43. package/css/marks.css +144 -0
  44. package/css/motion.css +88 -0
  45. package/css/overlay.css +52 -1
  46. package/css/primitives.css +18 -0
  47. package/css/report.css +363 -0
  48. package/css/selection.css +46 -0
  49. package/css/sources.css +179 -0
  50. package/css/spotlight.css +104 -0
  51. package/css/state.css +121 -0
  52. package/css/tokens.css +78 -60
  53. package/css/workbench.css +83 -0
  54. package/dist/bronto.css +1 -1
  55. package/dist/css/analytical.css +1 -0
  56. package/dist/css/annotations.css +1 -0
  57. package/dist/css/command.css +1 -0
  58. package/dist/css/connectors.css +1 -0
  59. package/dist/css/crosshair.css +1 -0
  60. package/dist/css/disclosure.css +1 -1
  61. package/dist/css/dots.css +1 -1
  62. package/dist/css/feedback.css +1 -1
  63. package/dist/css/fonts.css +1 -1
  64. package/dist/css/generated.css +1 -0
  65. package/dist/css/legend.css +1 -0
  66. package/dist/css/marks.css +1 -0
  67. package/dist/css/motion.css +1 -1
  68. package/dist/css/overlay.css +1 -1
  69. package/dist/css/primitives.css +1 -1
  70. package/dist/css/report.css +1 -0
  71. package/dist/css/selection.css +1 -0
  72. package/dist/css/sources.css +1 -0
  73. package/dist/css/spotlight.css +1 -0
  74. package/dist/css/state.css +1 -0
  75. package/dist/css/tokens.css +1 -1
  76. package/dist/css/workbench.css +1 -0
  77. package/docs/adr/0001-color-system.md +26 -27
  78. package/docs/adr/0002-scope-and-2026-baseline.md +104 -0
  79. package/docs/adr/0003-theme-model.md +97 -0
  80. package/docs/annotations.md +345 -0
  81. package/docs/architecture.md +202 -0
  82. package/docs/command.md +95 -0
  83. package/docs/connectors.md +91 -0
  84. package/docs/contrast.md +42 -42
  85. package/docs/crosshair.md +63 -0
  86. package/docs/generated.md +91 -0
  87. package/docs/legends.md +168 -0
  88. package/docs/marks.md +86 -0
  89. package/docs/reference.md +418 -15
  90. package/docs/reporting.md +305 -0
  91. package/docs/selection.md +40 -0
  92. package/docs/sources.md +110 -0
  93. package/docs/spotlight.md +78 -0
  94. package/docs/stability.md +52 -0
  95. package/docs/state.md +85 -0
  96. package/docs/theming.md +18 -6
  97. package/docs/usage.md +43 -3
  98. package/docs/workbench.md +72 -0
  99. package/fonts/doto-400.woff2 +0 -0
  100. package/fonts/doto-500.woff2 +0 -0
  101. package/fonts/doto-600.woff2 +0 -0
  102. package/fonts/doto-700.woff2 +0 -0
  103. package/fonts/doto-800.woff2 +0 -0
  104. package/fonts/doto-900.woff2 +0 -0
  105. package/llms.txt +288 -8
  106. package/package.json +95 -5
  107. package/qwik/index.d.ts +60 -0
  108. package/qwik/index.js +149 -0
  109. package/react/index.d.ts +40 -14
  110. package/react/index.js +32 -5
  111. package/solid/index.d.ts +40 -14
  112. package/solid/index.js +31 -3
  113. package/tokens/index.d.ts +3 -3
  114. package/tokens/index.js +25 -19
  115. package/tokens/index.json +32 -28
  116. package/tokens/resolved.json +34 -32
  117. package/tokens/tokens.dtcg.json +22 -14
  118. package/fonts/doto-400.ttf +0 -0
  119. package/fonts/doto-500.ttf +0 -0
  120. package/fonts/doto-600.ttf +0 -0
  121. package/fonts/doto-700.ttf +0 -0
  122. package/fonts/doto-800.ttf +0 -0
  123. package/fonts/doto-900.ttf +0 -0
@@ -0,0 +1,91 @@
1
+ # Connectors (leader lines)
2
+
3
+ `@ponchia/ui/css/connectors.css` + `@ponchia/ui/connectors` draw a **leader line
4
+ between two DOM elements** — connect a note to a card, a card to a chart point,
5
+ or two related regions. This is the page-coordinate, element-to-element cousin
6
+ of the figure-coordinate [annotations](./annotations.md) layer.
7
+
8
+ ```css
9
+ @import '@ponchia/ui';
10
+ @import '@ponchia/ui/css/connectors.css';
11
+ ```
12
+
13
+ Bronto computes the **geometry** (pure functions) and styles the line; the
14
+ optional `initConnectors` behavior draws and keeps it in sync. It owns no
15
+ layout, no scales — just the path between two rects.
16
+
17
+ ## Markup
18
+
19
+ An `.ui-connector` is an SVG that overlays a **positioned** container. Give it
20
+ the ids of the two elements to connect; `initConnectors` fills in the path.
21
+
22
+ ```html
23
+ <div style="position: relative">
24
+ <div id="card-a" class="ui-card">…</div>
25
+ <div id="note-b" class="ui-card">…</div>
26
+
27
+ <svg
28
+ class="ui-connector ui-connector--accent"
29
+ data-bronto-connector
30
+ data-from="card-a"
31
+ data-to="note-b"
32
+ data-shape="elbow"
33
+ data-end="arrow"
34
+ aria-hidden="true"
35
+ ></svg>
36
+ </div>
37
+ ```
38
+
39
+ ```js
40
+ import { initConnectors } from '@ponchia/ui/behaviors';
41
+ const stop = initConnectors(); // redraws on resize/scroll; returns a cleanup
42
+ ```
43
+
44
+ A connector is decorative — mark it `aria-hidden="true"` and make sure the
45
+ relationship it depicts is also clear from the content/DOM order.
46
+
47
+ | Attribute | Values |
48
+ | --- | --- |
49
+ | `data-from` / `data-to` | ids of the elements to connect (required). |
50
+ | `data-shape` | `straight` (default), `elbow`, `curve`. |
51
+ | `data-from-side` / `data-to-side` | `top`/`right`/`bottom`/`left`/`center`. Omit a side to auto-pick it (set neither for fully automatic facing edges). |
52
+ | `data-end` | `arrow` (default), `dot`, `none`. |
53
+
54
+ Tones: `ui-connector--accent` / `--muted` / `--success` / `--warning` /
55
+ `--danger` / `--info` (monochrome by default). `ui-connector--dashed` for a
56
+ dashed line; `ui-connector--draw` strokes it in once (reduced-motion-safe).
57
+ `ui.connector({ tone, dashed, motion })` builds the class string.
58
+
59
+ ## Geometry helpers (no DOM)
60
+
61
+ For SSR, canvas, or your own renderer, the pure helpers return SVG strings /
62
+ coordinates and never touch the DOM:
63
+
64
+ ```js
65
+ import { connectRects, arrowHead } from '@ponchia/ui/connectors';
66
+
67
+ const { d, to, angle } = connectRects({
68
+ fromRect: { x: 0, y: 0, width: 80, height: 32 },
69
+ toRect: { x: 220, y: 120, width: 80, height: 32 },
70
+ shape: 'curve',
71
+ });
72
+ const head = arrowHead(to, angle); // place at the endpoint
73
+ ```
74
+
75
+ - `anchorPoint(rect, side)` — a point on a rect's edge.
76
+ - `connectorPath({ from, to, shape, curvature, mid })` — path between two points.
77
+ - `straightPath` / `elbowPath` / `curvePath` — the individual shapes.
78
+ - `connectRects(opts)` → `{ d, from, to, angle }`.
79
+ - `arrowHead(point, angle, size)` / `dotMark(point, radius)` — end markers.
80
+ - `autoSides(fromRect, toRect)` / `angleBetween(from, to)`.
81
+ - `endTangentAngle(from, to, shape)` — the angle the path *arrives* at `to`
82
+ (chord for `straight`, axis-aligned for `elbow`/`curve`); rotate an end marker
83
+ by this so it points along the path. `connectRects().angle` already uses it.
84
+
85
+ ## Coordinate model
86
+
87
+ `initConnectors` measures the from/to elements relative to the connector SVG's
88
+ own box (via `getBoundingClientRect`), so place the SVG as an overlay
89
+ (`position: absolute; inset: 0`, which `.ui-connector` sets) inside a
90
+ `position: relative` container that holds both endpoints. The SVG has no
91
+ `viewBox`, so its user units are CSS pixels.
package/docs/contrast.md CHANGED
@@ -65,26 +65,26 @@ Overall: **all contractual pairings meet their floor ✅**.
65
65
 
66
66
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
67
67
  | --- | --- | --- | --- | --- | --- | --- |
68
- | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 18.76:1 | Lc 99 | ✅ pass |
69
- | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 17.47:1 | Lc 99 | ✅ pass |
70
- | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 15.55:1 | Lc 98 | ✅ pass |
71
- | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 12.04:1 | Lc 71 | ✅ pass |
72
- | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 11.22:1 | Lc 71 | ✅ pass |
73
- | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.98:1 | Lc 70 | ✅ pass |
74
- | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 5.69:1 | Lc 37 | ✅ pass |
75
- | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 5.30:1 | Lc 37 | ✅ pass |
76
- | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 4.72:1 | Lc 36 | ✅ pass |
77
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.88:1 | Lc 45 | ✅ pass |
78
- | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 6.41:1 | Lc 45 | ✅ pass |
68
+ | `--text` | `--bg` | Body text on page background | AA text (4.5:1) | 15.01:1 | Lc 91 | ✅ pass |
69
+ | `--text` | `--surface` | Body text on a card/panel | AA text (4.5:1) | 13.65:1 | Lc 90 | ✅ pass |
70
+ | `--text` | `--surface-muted` | Body text on a muted panel | AA text (4.5:1) | 12.44:1 | Lc 89 | ✅ pass |
71
+ | `--text-soft` | `--bg` | Secondary text on page background | AA text (4.5:1) | 11.20:1 | Lc 73 | ✅ pass |
72
+ | `--text-soft` | `--surface` | Secondary text on a card | AA text (4.5:1) | 10.19:1 | Lc 72 | ✅ pass |
73
+ | `--text-soft` | `--surface-muted` | Secondary text on a muted panel | AA text (4.5:1) | 9.28:1 | Lc 71 | ✅ pass |
74
+ | `--text-dim` | `--bg` | Dim/meta text on page background | AA text (4.5:1) | 7.16:1 | Lc 50 | ✅ pass |
75
+ | `--text-dim` | `--surface` | Dim/meta text on a card | AA text (4.5:1) | 6.52:1 | Lc 49 | ✅ pass |
76
+ | `--text-dim` | `--surface-muted` | Dim/meta text on a muted panel | AA text (4.5:1) | 5.94:1 | Lc 48 | ✅ pass |
77
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 6.14:1 | Lc 45 | ✅ pass |
78
+ | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 5.58:1 | Lc 44 | ✅ pass |
79
79
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 5.95:1 | Lc 43 | ✅ pass |
80
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.95:1 | Lc 41 | ✅ pass |
81
- | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 5.55:1 | Lc 40 | ✅ pass |
82
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.95:1 | Lc 41 | ✅ pass |
83
- | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 8.70:1 | Lc 58 | ✅ pass |
84
- | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 10.66:1 | Lc 68 | ✅ pass |
85
- | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 6.00:1 | Lc 43 | ✅ pass |
86
- | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 8.41:1 | Lc 56 | ✅ pass |
87
- | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.01:1 | Lc 10 | ℹ️ not gated |
80
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 5.31:1 | Lc 40 | ✅ pass |
81
+ | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 4.83:1 | Lc 39 | ✅ pass |
82
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 5.31:1 | Lc 40 | ✅ pass |
83
+ | `--success` | `--surface` | Success indicator vs a card | UI / large (3:1) | 7.58:1 | Lc 57 | ✅ pass |
84
+ | `--warning` | `--surface` | Warning indicator vs a card | UI / large (3:1) | 9.29:1 | Lc 67 | ✅ pass |
85
+ | `--danger` | `--surface` | Danger indicator vs a card | UI / large (3:1) | 5.23:1 | Lc 42 | ✅ pass |
86
+ | `--info` | `--surface` | Info indicator vs a card | UI / large (3:1) | 7.33:1 | Lc 55 | ✅ pass |
87
+ | `--line-strong` | `--surface` | Strong hairline vs a card | Decorative (1.4.11-exempt) | 2.29:1 | Lc 15 | ℹ️ not gated |
88
88
 
89
89
  ## Display colorways (skins)
90
90
 
@@ -112,12 +112,12 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
112
112
 
113
113
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
114
114
  | --- | --- | --- | --- | --- | --- | --- |
115
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.97:1 | Lc 75 | ✅ pass |
116
- | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 12.08:1 | Lc 75 | ✅ pass |
115
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 11.57:1 | Lc 75 | ✅ pass |
116
+ | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 10.52:1 | Lc 74 | ✅ pass |
117
117
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 11.88:1 | Lc 72 | ✅ pass |
118
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.88:1 | Lc 70 | ✅ pass |
119
- | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.07:1 | Lc 70 | ✅ pass |
120
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.88:1 | Lc 70 | ✅ pass |
118
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 10.60:1 | Lc 70 | ✅ pass |
119
+ | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 9.64:1 | Lc 69 | ✅ pass |
120
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 10.60:1 | Lc 70 | ✅ pass |
121
121
 
122
122
  ### E-ink — light
123
123
 
@@ -134,12 +134,12 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
134
134
 
135
135
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
136
136
  | --- | --- | --- | --- | --- | --- | --- |
137
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 13.98:1 | Lc 80 | ✅ pass |
138
- | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.02:1 | Lc 79 | ✅ pass |
137
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.47:1 | Lc 79 | ✅ pass |
138
+ | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.35:1 | Lc 78 | ✅ pass |
139
139
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 12.86:1 | Lc 76 | ✅ pass |
140
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.86:1 | Lc 75 | ✅ pass |
141
- | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.98:1 | Lc 74 | ✅ pass |
142
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.86:1 | Lc 75 | ✅ pass |
140
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 11.48:1 | Lc 74 | ✅ pass |
141
+ | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 10.44:1 | Lc 73 | ✅ pass |
142
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 11.48:1 | Lc 74 | ✅ pass |
143
143
 
144
144
  ### Phosphor Green — light
145
145
 
@@ -156,12 +156,12 @@ palette untouched). Accents are authored in OKLCH; `--accent-text` is the
156
156
 
157
157
  | Foreground | Background | Role | Held to | Ratio | APCA _(advisory)_ | Verdict |
158
158
  | --- | --- | --- | --- | --- | --- | --- |
159
- | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 14.54:1 | Lc 82 | ✅ pass |
160
- | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 13.55:1 | Lc 82 | ✅ pass |
159
+ | `--accent-text` | `--bg` | Accent text on page background | AA text (4.5:1) | 12.97:1 | Lc 82 | ✅ pass |
160
+ | `--accent-text` | `--surface` | Accent text on a card | AA text (4.5:1) | 11.80:1 | Lc 81 | ✅ pass |
161
161
  | `--button-text` | `--accent` | Label on the primary button | AA text (4.5:1) | 13.75:1 | Lc 80 | ✅ pass |
162
- | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 13.75:1 | Lc 79 | ✅ pass |
163
- | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 12.81:1 | Lc 79 | ✅ pass |
164
- | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 13.75:1 | Lc 79 | ✅ pass |
162
+ | `--focus-ring` | `--bg` | Focus ring vs page background | UI / large (3:1) | 12.27:1 | Lc 79 | ✅ pass |
163
+ | `--focus-ring` | `--surface` | Focus ring vs a card | UI / large (3:1) | 11.16:1 | Lc 78 | ✅ pass |
164
+ | `--accent` | `--bg` | Accent fill vs page background | UI / large (3:1) | 12.27:1 | Lc 79 | ✅ pass |
165
165
 
166
166
  ## Data-viz palette (advisory)
167
167
 
@@ -191,14 +191,14 @@ the brand accent.
191
191
 
192
192
  | Series | Colour | Ratio _(advisory)_ | APCA _(advisory)_ |
193
193
  | --- | --- | --- | --- |
194
- | 1 _(accent)_ | `#ff3b41` | 5.95:1 | Lc 41 |
195
- | 2 | `#e69f00` | 9.32:1 | Lc 58 |
196
- | 3 | `#56b4e9` | 9.10:1 | Lc 57 |
197
- | 4 | `#009e73` | 6.14:1 | Lc 41 |
198
- | 5 | `#f0e442` | 15.88:1 | Lc 88 |
199
- | 6 | `#0072b2` | 4.05:1 | Lc 27 |
200
- | 7 | `#cc79a7` | 6.86:1 | Lc 45 |
201
- | 8 | `#4d5358` | 2.69:1 | Lc 15 |
194
+ | 1 _(accent)_ | `#ff3b41` | 5.31:1 | Lc 40 |
195
+ | 2 | `#e69f00` | 8.32:1 | Lc 58 |
196
+ | 3 | `#56b4e9` | 8.12:1 | Lc 56 |
197
+ | 4 | `#009e73` | 5.48:1 | Lc 40 |
198
+ | 5 | `#f0e442` | 14.17:1 | Lc 87 |
199
+ | 6 | `#0072b2` | 3.61:1 | Lc 26 |
200
+ | 7 | `#cc79a7` | 6.12:1 | Lc 44 |
201
+ | 8 | `#4d5358` | 2.40:1 | Lc 14 |
202
202
 
203
203
  ## Scope & caveats
204
204
 
@@ -0,0 +1,63 @@
1
+ # Crosshair & readout
2
+
3
+ `@ponchia/ui/css/crosshair.css` is an opt-in **ruler + readout** for reading a
4
+ value off a plot: pointer-tracking crosshair rules, axis value badges, and a
5
+ pinned readout chip.
6
+
7
+ ```css
8
+ @import '@ponchia/ui';
9
+ @import '@ponchia/ui/css/crosshair.css';
10
+ ```
11
+
12
+ Bronto draws the rules and (via `initCrosshair`) tracks the pointer — it reports
13
+ **where** the pointer is, as pixels and 0–1 fractions. It does **not** find the
14
+ nearest data point or map pixels to data values; that needs your scales. So this
15
+ is the *visual + pointer* layer, not a chart engine.
16
+
17
+ ## Markup
18
+
19
+ The plot is `[data-bronto-crosshair]` (a `position: relative` box); it contains a
20
+ `.ui-crosshair` overlay with the rule(s) you want.
21
+
22
+ ```html
23
+ <figure data-bronto-crosshair style="position: relative">
24
+ <!-- your chart / image / canvas -->
25
+ <div class="ui-crosshair" aria-hidden="true">
26
+ <div class="ui-crosshair__line ui-crosshair__line--x"></div>
27
+ <div class="ui-crosshair__line ui-crosshair__line--y"></div>
28
+ <div class="ui-readout" id="readout"></div>
29
+ </div>
30
+ </figure>
31
+ ```
32
+
33
+ ```js
34
+ import { initCrosshair } from '@ponchia/ui/behaviors';
35
+ const stop = initCrosshair(); // returns a cleanup
36
+ document.querySelector('[data-bronto-crosshair]').addEventListener(
37
+ 'bronto:crosshair:move',
38
+ (e) => {
39
+ const { x, y, fx, fy } = e.detail; // px + 0..1 fractions
40
+ // YOU map the fraction to a data value with your scale:
41
+ readout.textContent = `${(fx * 100).toFixed(0)}%`;
42
+ },
43
+ );
44
+ ```
45
+
46
+ | Class | Role |
47
+ | --- | --- |
48
+ | `ui-crosshair` | The overlay. Hidden until `.is-active` (set on the first pointer move over the plot). |
49
+ | `ui-crosshair__line` + `--x` / `--y` | Vertical / horizontal rule, positioned by `--crosshair-x` / `--crosshair-y`. |
50
+ | `ui-crosshair__badge` | An axis value chip (you set its text + edge). |
51
+ | `ui-readout` | A pinned readout chip; follows the crosshair point. |
52
+
53
+ `ui-crosshair--muted` is a subtler neutral crosshair. `ui.crosshair({ muted })`
54
+ builds the class string. Include only the lines you need (just `--x` for a
55
+ vertical scrubber, etc.).
56
+
57
+ ## Events
58
+
59
+ - `bronto:crosshair:move` — `{ x, y, fx, fy }` (px within the plot + fractions).
60
+ - `bronto:crosshair:leave` — pointer left the plot.
61
+
62
+ The crosshair is decorative (`aria-hidden`); expose the underlying values to
63
+ assistive tech through a data table or text, not the rules.
@@ -0,0 +1,91 @@
1
+ # Generated content & AI trust
2
+
3
+ `@ponchia/ui/css/generated.css` is an opt-in layer of **trust surfaces for AI /
4
+ system-generated content**. Bronto is not a chat framework — it does not ship
5
+ chat bubbles. It ships the surfaces that make generated content legible and
6
+ accountable: a marked region, an origin label, and quiet collapsible reasoning
7
+ and tool-call logs. It pairs with the [source & provenance layer](./sources.md).
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/generated.css';
12
+ ```
13
+
14
+ Bronto styles the disclosure, origin, and trace surfaces. The host owns model
15
+ metadata, reasoning-visibility policy, tool execution, redaction, and safety.
16
+ Prefer evidence and source links over decorative "AI sparkle" styling. Not in
17
+ the core bundle.
18
+
19
+ ## Generated region — `.ui-generated`
20
+
21
+ Wraps a block of machine-authored content with a quiet accent edge and an
22
+ optional origin `__label`.
23
+
24
+ ```html
25
+ <section class="ui-generated">
26
+ <p class="ui-generated__label">Model output</p>
27
+ <p>The migration cut p95 latency by 38% in the first hour.</p>
28
+ </section>
29
+ ```
30
+
31
+ ## Origin label — `.ui-origin-label`
32
+
33
+ A small tag for "AI assisted", "model output", "tool output", or "human
34
+ reviewed". `--ai` accent-tints it for model-generated origin; the default is a
35
+ neutral tag. The words carry the meaning — the tint is reinforcement.
36
+
37
+ ```html
38
+ <span class="ui-origin-label ui-origin-label--ai">AI generated</span>
39
+ <span class="ui-origin-label">Human reviewed</span>
40
+ ```
41
+
42
+ ## Reasoning — `.ui-reasoning`
43
+
44
+ A quiet, collapsible "how this was produced" block on a native `<details>` (zero
45
+ JS). Keep it closed by default; the host decides what reasoning is safe to show.
46
+
47
+ ```html
48
+ <details class="ui-reasoning">
49
+ <summary>Reasoning</summary>
50
+ <p class="ui-reasoning__body">Compared the Q2 and Q3 latency tables, then…</p>
51
+ </details>
52
+ ```
53
+
54
+ ## Tool log — `.ui-tool-log` / `.ui-tool-call`
55
+
56
+ A document-grade record of tool invocations. Each `.ui-tool-call` is a
57
+ collapsible `<details>`: a `__name`, an optional `__status` (put a `.ui-dot` or
58
+ text), and a `__body` with the arguments or output.
59
+
60
+ ```html
61
+ <div class="ui-tool-log">
62
+ <details class="ui-tool-call">
63
+ <summary>
64
+ <span class="ui-tool-call__name">search_docs(query)</span>
65
+ <span class="ui-tool-call__status"><span class="ui-dot ui-dot--live"></span> ok</span>
66
+ </summary>
67
+ <pre class="ui-tool-call__body">{ "query": "p95 latency", "hits": 3 }</pre>
68
+ </details>
69
+ </div>
70
+ ```
71
+
72
+ ## Recipe
73
+
74
+ ```js
75
+ import { ui } from '@ponchia/ui/classes';
76
+
77
+ ui.originLabel({ ai: true }); // "ui-origin-label ui-origin-label--ai"
78
+ ```
79
+
80
+ The other parts are class-only (`cls.generated`, `cls.toolCall`, …) — they have
81
+ no options, so apply the class directly.
82
+
83
+ ## Accessibility
84
+
85
+ - The reasoning and tool-call disclosures are native `<details>` — keyboard- and
86
+ screen-reader-accessible with no JS.
87
+ - An origin label must read as text ("AI generated"); the accent tint is not the
88
+ only signal.
89
+ - A confidence / verdict widget is intentionally **not** shipped: don't
90
+ fabricate precision. Add a confidence surface only when the product has a real
91
+ signal to show.
@@ -0,0 +1,168 @@
1
+ # Legends & data keys
2
+
3
+ `@ponchia/ui/css/legend.css` is an opt-in layer of **data keys** for charts,
4
+ reports, and analytical figures. A legend maps a visual encoding (colour, a
5
+ pattern, a shape, a gradient) to its meaning. It reads the same Tier-4
6
+ `--chart-*` tokens the [data-viz palette](./reference.md) ships, so a key never
7
+ drifts from the series it describes.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/dataviz.css';
12
+ @import '@ponchia/ui/css/legend.css';
13
+ ```
14
+
15
+ Bronto **paints and positions** the key; it owns no scales, no data→pixel
16
+ mapping, and no series state. Pair it with Bronto's own `.ui-chart` bars, an
17
+ SVG/canvas figure, or any external chart engine — the host owns the chart.
18
+
19
+ ## What it is not
20
+
21
+ - Not a chart engine. It does not compute tick positions, "nice" numbers,
22
+ bin thresholds, or bubble radii — you supply the tick/range **text**; the
23
+ CSS positions the slots you give it.
24
+ - Not a colour source. Swatch colour always comes from a `--chart-*` token
25
+ (enforced by `check:legend`), never a hand-rolled hex.
26
+
27
+ ## Accessibility: colour is never the only channel
28
+
29
+ A legend that distinguishes series by **colour alone** fails
30
+ [WCAG 1.4.1 Use of Color](https://www.w3.org/WAI/WCAG21/Understanding/use-of-color.html).
31
+ In a legend, the **text label** is the required non-colour channel — every
32
+ entry carries its name, so the meaning survives even if colour is lost
33
+ (`forced-colors`, monochrome print, colour-vision deficiency). Where the chart
34
+ *mark itself* is colour-only (a bare area or line), also pair the swatch with
35
+ its `--chart-pattern-*` so the figure — not just the legend — stays readable.
36
+
37
+ Recommended structure: wrap the figure and its key in a `<figure>` with a
38
+ `<figcaption>`, and give the legend its own group label.
39
+
40
+ ```html
41
+ <figure role="group" aria-labelledby="fig-1-title">
42
+ <figcaption id="fig-1-title">Fig 1 — Weekly focus split</figcaption>
43
+
44
+ <!-- … the chart … -->
45
+
46
+ <ul class="ui-legend" aria-label="Series">
47
+ <li class="ui-legend__item">
48
+ <span
49
+ class="ui-legend__swatch"
50
+ style="--chart-color: var(--chart-1); --chart-pattern: var(--chart-pattern-1)"
51
+ aria-hidden="true"
52
+ ></span>
53
+ <span class="ui-legend__label">Research</span>
54
+ </li>
55
+ <li class="ui-legend__item">
56
+ <span class="ui-legend__swatch ui-legend__swatch--2" aria-hidden="true"></span>
57
+ <span class="ui-legend__label">Delivery</span>
58
+ </li>
59
+ </ul>
60
+ </figure>
61
+ ```
62
+
63
+ The swatch is decorative (`aria-hidden="true"`) — the meaning is the label
64
+ text. Set its colour either inline (`--chart-color`) to mirror exactly what the
65
+ chart mark uses, or with a `--N` index helper for the categorical palette.
66
+
67
+ ## Parts
68
+
69
+ | Class | Role |
70
+ | --- | --- |
71
+ | `ui-legend` | The container (a wrapping inline row by default). |
72
+ | `ui-legend__title` | Optional heading for the key. |
73
+ | `ui-legend__item` | One entry — swatch + label (+ value). |
74
+ | `ui-legend__swatch` | The colour/pattern chip. |
75
+ | `ui-legend__symbol` | A glyph/shape chip (fill an `.ui-icon` mask). |
76
+ | `ui-legend__label` | The series name (the non-colour channel). |
77
+ | `ui-legend__value` | Optional trailing value/range. |
78
+ | `ui-legend__caption` | Optional footnote (units, source). |
79
+ | `ui-legend__track` | The gradient bar (continuous keys). |
80
+ | `ui-legend__ticks` / `ui-legend__tick` | Tick labels under the track. |
81
+
82
+ ### Swatch colour
83
+
84
+ | Approach | Use |
85
+ | --- | --- |
86
+ | `style="--chart-color: var(--chart-3)"` | Mirror any token, any order; add `--chart-pattern` to match a patterned mark. |
87
+ | `class="… ui-legend__swatch--3"` | Categorical palette series 1–8 — sets `--chart-3` for you. |
88
+
89
+ `ui-legend__swatch--circle` and `ui-legend__swatch--line` change the chip shape
90
+ (dot series, line series).
91
+
92
+ ## Variants
93
+
94
+ | Modifier | Effect |
95
+ | --- | --- |
96
+ | `ui-legend--vertical` | Stack entries instead of the wrapping row. |
97
+ | `ui-legend--compact` | Denser type and gaps. |
98
+ | `ui-legend--with-values` | Align a trailing `__value` column across rows. |
99
+ | `ui-legend--gradient` | Continuous colour ramp (`__track` + `__ticks`). |
100
+ | `ui-legend--diverging` | Use the 7-stop diverging ramp (with `--gradient`). |
101
+ | `ui-legend--threshold` | Binned `swatch │ range-label` grid. |
102
+ | `ui-legend--interactive` | Entries are toggle controls (see below). |
103
+
104
+ The recipe mirrors this surface:
105
+
106
+ ```js
107
+ import { ui } from '@ponchia/ui/classes';
108
+
109
+ ui.legend({ type: 'gradient', diverging: true });
110
+ // "ui-legend ui-legend--gradient ui-legend--diverging"
111
+ ui.legendSwatch({ series: 3, shape: 'circle' });
112
+ // "ui-legend__swatch ui-legend__swatch--3 ui-legend__swatch--circle"
113
+ ```
114
+
115
+ ### Continuous ramp
116
+
117
+ Supply the min/mid/max tick **text**; the track interpolates the sequential
118
+ ramp in OKLCH (`--diverging` swaps in the diverging ramp around its neutral
119
+ centre).
120
+
121
+ ```html
122
+ <div class="ui-legend ui-legend--gradient" role="group" aria-label="Density">
123
+ <span class="ui-legend__track" aria-hidden="true"></span>
124
+ <span class="ui-legend__ticks">
125
+ <span class="ui-legend__tick">0</span>
126
+ <span class="ui-legend__tick">50</span>
127
+ <span class="ui-legend__tick">100</span>
128
+ </span>
129
+ </div>
130
+ ```
131
+
132
+ ## Interactive legends (optional)
133
+
134
+ An interactive legend toggles a series on or off. Bronto ships the **control
135
+ surface** only; the **host owns the data**. The split is deliberate (it keeps
136
+ the legend out of chart-engine territory):
137
+
138
+ - **Bronto:** each entry is a `<button aria-pressed>`. The optional
139
+ `initLegend` behavior flips `aria-pressed`, toggles `.is-inactive`, and
140
+ dispatches `bronto:legend:toggle` with `{ detail: { series, active } }`.
141
+ - **You:** listen for the event, hide/show your own series, and announce the
142
+ change through an `aria-live` region you own.
143
+
144
+ ```html
145
+ <ul class="ui-legend ui-legend--interactive" data-bronto-legend aria-label="Series">
146
+ <li>
147
+ <button type="button" class="ui-legend__item" aria-pressed="true" data-series="research">
148
+ <span class="ui-legend__swatch ui-legend__swatch--1" aria-hidden="true"></span>
149
+ <span class="ui-legend__label">Research</span>
150
+ </button>
151
+ </li>
152
+ </ul>
153
+ ```
154
+
155
+ ```js
156
+ import { initLegend } from '@ponchia/ui/behaviors';
157
+ const stop = initLegend(); // returns a cleanup fn
158
+ document.addEventListener('bronto:legend:toggle', (e) => {
159
+ const { series, active } = e.detail;
160
+ // hide/show your series, then announce it in your own aria-live region
161
+ });
162
+ ```
163
+
164
+ Convention: `aria-pressed="true"` means the series is **shown** (the default).
165
+ The entry's label never changes on toggle — only `aria-pressed` and
166
+ `.is-inactive` flip, so a screen reader reads a stable name with a clear
167
+ pressed state. React/Solid/Qwik consumers can use `useLegend()` instead of
168
+ calling `initLegend` directly.
package/docs/marks.md ADDED
@@ -0,0 +1,86 @@
1
+ # Text marks & evidence
2
+
3
+ `@ponchia/ui/css/marks.css` is an opt-in layer of **evidence and emphasis marks
4
+ for running text** — the prose counterpart to the [SVG annotations](./annotations.md)
5
+ layer. Annotations call out a figure; marks call out a sentence. The look is
6
+ sober and report-grade (good for docs, audits, and generated/LLM reports), not
7
+ a hand-drawn highlighter.
8
+
9
+ ```css
10
+ @import '@ponchia/ui';
11
+ @import '@ponchia/ui/css/marks.css';
12
+ ```
13
+
14
+ Monochrome by default — the rationed accent is opt-in via `--accent`, and the
15
+ status tones are only for status-bearing emphasis.
16
+
17
+ ## Inline marks — `.ui-mark`
18
+
19
+ Put `.ui-mark` on a `<mark>` so the emphasis is semantic, not just visual.
20
+
21
+ ```html
22
+ <p>
23
+ The migration <mark class="ui-mark ui-mark--accent">cut p95 latency by 38%</mark>,
24
+ but <mark class="ui-mark ui-mark--danger ui-mark--underline">error rate doubled</mark>
25
+ in the first hour.
26
+ </p>
27
+ ```
28
+
29
+ | Style | Effect |
30
+ | --- | --- |
31
+ | _(default)_ | Highlighter fill. |
32
+ | `ui-mark--underline` | Coloured underline (no fill). |
33
+ | `ui-mark--box` | Outlined box. |
34
+ | `ui-mark--strike` | Strikethrough (removed/superseded text). |
35
+
36
+ | Tone | Use |
37
+ | --- | --- |
38
+ | _(default)_ | Neutral ink emphasis (monochrome). |
39
+ | `ui-mark--accent` | The rationed accent — "this is the proof". |
40
+ | `ui-mark--success` / `--warning` / `--danger` / `--info` | Status-bearing emphasis only. |
41
+ | `ui-mark--muted` | De-emphasis. |
42
+
43
+ `ui-mark--draw` sweeps the highlight in once on load; it respects
44
+ `prefers-reduced-motion` (reduced motion shows the resting full highlight). It
45
+ applies to the highlight fill, so pair it with the default (no style modifier).
46
+
47
+ ## Passage bracket — `.ui-bracket-note`
48
+
49
+ Brackets a whole block and optionally labels it — the prose analogue of
50
+ `ui-annotation--bracket`. Useful for "this paragraph is the evidence/caveat".
51
+
52
+ ```html
53
+ <blockquote class="ui-bracket-note ui-bracket-note--accent">
54
+ <span class="ui-bracket-note__label">Source</span>
55
+ Q3 incident review, §4 — sustained for 47 minutes before rollback.
56
+ </blockquote>
57
+ ```
58
+
59
+ Tones: `--accent` (the rationed accent), `--warning`, `--danger`, `--info`. The
60
+ default is a neutral bracket.
61
+
62
+ ## Recipes
63
+
64
+ ```js
65
+ import { ui } from '@ponchia/ui/classes';
66
+
67
+ ui.mark({ tone: 'accent', motion: 'draw' });
68
+ // "ui-mark ui-mark--accent ui-mark--draw"
69
+ ui.mark({ style: 'underline', tone: 'danger' });
70
+ // "ui-mark ui-mark--underline ui-mark--danger"
71
+ ui.bracketNote({ tone: 'warning' });
72
+ // "ui-bracket-note ui-bracket-note--warning"
73
+ ```
74
+
75
+ ## Accessibility
76
+
77
+ - A `.ui-mark` is **visual emphasis**; it does not announce itself to screen
78
+ readers. When the emphasis carries meaning that the surrounding words don't,
79
+ state it in the text (a screen-reader user can't see the highlight) — the same
80
+ rule as colour (WCAG 1.4.1). Use a native `<mark>` so the relationship is at
81
+ least semantic.
82
+ - In `forced-colors` mode a highlight `background` is dropped, so marks add an
83
+ underline to keep the emphasis visible; the `--box`/`--underline`/`--strike`
84
+ styles already survive as a system colour.
85
+ - `.ui-bracket-note` is a plain block; wrap a quotation in `<blockquote>` (or a
86
+ region with its own heading/label) so its role is conveyed without the border.