@nanisoft/prism-ui 0.15.0 → 0.16.1

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 (47) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/blocks/data-table-01/data-table.d.ts +10 -0
  3. package/dist/blocks/data-table-01/data-table.d.ts.map +1 -1
  4. package/dist/blocks/data-table-01/data-table.js +2 -1
  5. package/dist/blocks/page-header-01/page-header.d.ts +7 -0
  6. package/dist/blocks/page-header-01/page-header.d.ts.map +1 -1
  7. package/dist/blocks/page-header-01/page-header.js +9 -1
  8. package/dist/blocks/run-console-01/run-console.d.ts.map +1 -1
  9. package/dist/blocks/run-console-01/run-console.js +2 -1
  10. package/dist/blocks/site-footer/site-footer.d.ts +16 -0
  11. package/dist/blocks/site-footer/site-footer.d.ts.map +1 -1
  12. package/dist/blocks/site-footer/site-footer.js +17 -1
  13. package/dist/blocks/stack-grid-01/stack-grid.d.ts +13 -0
  14. package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -1
  15. package/dist/blocks/stack-grid-01/stack-grid.js +14 -1
  16. package/dist/components/ui/diagram.d.ts +38 -7
  17. package/dist/components/ui/diagram.d.ts.map +1 -1
  18. package/dist/components/ui/diagram.js +134 -52
  19. package/dist/components/ui/fact-list.d.ts +17 -0
  20. package/dist/components/ui/fact-list.d.ts.map +1 -1
  21. package/dist/components/ui/fact-list.js +18 -1
  22. package/dist/components/ui/prose.d.ts +11 -0
  23. package/dist/components/ui/prose.d.ts.map +1 -1
  24. package/dist/components/ui/prose.js +12 -1
  25. package/dist/components/ui/pulse-graph.d.ts +36 -4
  26. package/dist/components/ui/pulse-graph.d.ts.map +1 -1
  27. package/dist/components/ui/pulse-graph.js +174 -60
  28. package/dist/components/ui/section.d.ts +33 -31
  29. package/dist/components/ui/section.d.ts.map +1 -1
  30. package/dist/components/ui/section.js +33 -31
  31. package/dist/lib/figure.d.ts +149 -0
  32. package/dist/lib/figure.d.ts.map +1 -0
  33. package/dist/lib/figure.js +242 -0
  34. package/dist/live/run-stream-01/run-stream.d.ts.map +1 -1
  35. package/dist/live/run-stream-01/run-stream.js +2 -1
  36. package/dist/pages/blog-post-page/index.d.ts +36 -15
  37. package/dist/pages/blog-post-page/index.d.ts.map +1 -1
  38. package/dist/pages/blog-post-page/index.js +35 -10
  39. package/dist/pages/docs-shell/docs-shell.d.ts +131 -17
  40. package/dist/pages/docs-shell/docs-shell.d.ts.map +1 -1
  41. package/dist/pages/docs-shell/docs-shell.js +230 -24
  42. package/dist/pages/error-page/index.d.ts.map +1 -1
  43. package/dist/pages/error-page/index.js +7 -3
  44. package/dist/pages/not-found-page/index.d.ts.map +1 -1
  45. package/dist/pages/not-found-page/index.js +4 -3
  46. package/dist/styles.css +131 -12
  47. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { INK_CLEARANCE, LABEL_DESCENT, fitPoints, strokeBetween, } from '../../lib/figure.js';
2
3
  import { cn } from '../../lib/utils.js';
3
4
  /**
4
5
  * The canvas the graph draws into, in its own user units.
@@ -7,18 +8,49 @@ import { cn } from '../../lib/utils.js';
7
8
  * the reason `Diagram` gives: type inside a scaled SVG is sized in the SVG's
8
9
  * own user units, so a viewBox derived from a caller's coordinates would make
9
10
  * the label size a function of how large a number the caller happened to use.
11
+ *
12
+ * **A fixed canvas fixes the ratio, and the ratio was the half of the argument
13
+ * that was missing.** Every label's rendered size is its size in user units times
14
+ * the rendered width over this number, so a drawing that is right at a desktop
15
+ * width shrinks with its container until it says nothing. In the 356 pixels a
16
+ * phone leaves a PulseGraph, a stage name came to 7.23 pixels and a node's note
17
+ * to 5.56, which is a picture of a shape with nothing written on it. The canvas
18
+ * is still the right answer and it is what keeps a label a constant rather than a
19
+ * function of the caller's arithmetic; what it does not do is set a floor, and
20
+ * `LEGIBLE_MIN_WIDTH` is that floor. `DESIGN.md` states the rule under The figure
21
+ * floor.
10
22
  */
11
23
  const CANVAS_WIDTH = 640;
12
24
  const CANVAS_HEIGHT = 360;
25
+ /**
26
+ * The width below which the drawing stops being legible, as a spacing multiple.
27
+ *
28
+ * The floor is `CANVAS_WIDTH` itself, so the rule is one sentence: the drawing is
29
+ * never rendered narrower than its own coordinate space, one user unit is
30
+ * therefore never less than one pixel, and the smallest label in it is
31
+ * `NODE_NOTE_SIZE`, which is the size `--text-xs` resolves to. So no label this
32
+ * drawing sets is smaller than `text-xs`, and at the floor the smallest is
33
+ * exactly it.
34
+ *
35
+ * **A container narrower than the floor scrolls rather than shrinking, and that
36
+ * is the answer this repository already gives a table and a board.** A table that
37
+ * restacks has stopped being a table, and a graph whose stage names are seven
38
+ * pixels tall has stopped being a graph: it is the shape with nothing written on
39
+ * it a reader can read. So the drawing holds its size and the container moves
40
+ * under it, which is what `Table` does with seven columns and what `Gantt01`
41
+ * does with a schedule of names and bars.
42
+ *
43
+ * `160` is `CANVAS_WIDTH` as a multiple of the authored spacing base, which is
44
+ * how a width nobody held a separate decision for is written.
45
+ */
46
+ const LEGIBLE_MIN_WIDTH = 'min-w-160';
13
47
  /**
14
48
  * The band left empty around the fitted points, in user units.
15
49
  *
16
- * Wider than `Diagram`'s, and the extra is on the bottom rather than split. A
17
- * pulse graph is read as a sequence running left to right, so the bottom band
18
- * carries the rail, the lane marks and the stage names set under them, and all
19
- * three are horizontal extents that a graph with more vertical spread would
20
- * collide with. Reserving it once here is cheaper than every caller learning
21
- * where not to put a node.
50
+ * The same band `Diagram` keeps on its top and its two sides, and the foot
51
+ * carries more than that: a pulse graph is read as a sequence running left to
52
+ * right, so the band below the fitted area holds the rail, and a lane whose mark
53
+ * lands low puts its note in the gap `RAIL_BAND` left rather than under the rail.
22
54
  */
23
55
  const PADDING = 56;
24
56
  /** The node mark's radius. A circle, so no corner radius for the pack to move. */
@@ -29,14 +61,29 @@ const NODE_RADIUS = 6;
29
61
  * The authored type scale is in `rem` and cannot be used inside a viewBox, where
30
62
  * a font size is a coordinate and not a length on the page. These are geometry,
31
63
  * the one thing Tailwind stays authoritative for.
64
+ *
65
+ * The note is twelve user units, which is what `--text-xs` resolves to, so at
66
+ * `LEGIBLE_MIN_WIDTH` it renders at exactly that step and never below it. It was
67
+ * ten, and ten is not a step this system sets interface type at, so a note was the
68
+ * smallest type the whole package emitted anywhere, a figure included. The name is
69
+ * one unit larger, which is the same hierarchy the two have always drawn.
32
70
  */
33
71
  const NODE_NAME_SIZE = 13;
34
72
  const NODE_NAME_OFFSET = 22;
35
- const NODE_NOTE_SIZE = 10;
36
- const NODE_NOTE_OFFSET = 35;
73
+ const NODE_NOTE_SIZE = 12;
74
+ /**
75
+ * The note's baseline, measured down from the mark's centre.
76
+ *
77
+ * The name's baseline, then the note's own size, then a line of leading, so the
78
+ * second line sits under the first rather than on it. It is the deepest any node
79
+ * draws, and the rail band below is measured from it.
80
+ */
81
+ const NODE_NOTE_OFFSET = NODE_NAME_OFFSET + NODE_NOTE_SIZE + 5;
82
+ /** The deepest ink a node draws below its mark's centre, in user units. */
83
+ const NODE_NOTE_DEPTH = NODE_NOTE_OFFSET + LABEL_DESCENT * NODE_NOTE_SIZE;
37
84
  /**
38
85
  * The rail the marker rides, in user units, measured from the bottom of the
39
- * fitted area.
86
+ * canvas.
40
87
  *
41
88
  * It is a separate element from the edges because it is a different claim: the
42
89
  * edges say what connects to what, and the rail says there is an order. A graph
@@ -46,28 +93,64 @@ const NODE_NOTE_OFFSET = 35;
46
93
  const RAIL_OFFSET = 74;
47
94
  const RAIL_THICKNESS = 1.5;
48
95
  /**
49
- * The caller's points, fitted into the canvas with their aspect ratio kept and
50
- * the leftover shared out evenly.
96
+ * The band the rail reserves at the foot of the canvas, in user units.
51
97
  *
52
- * A caller whose points span one axis only, or who passes a single node, gets
53
- * the other axis centred rather than a division by zero.
98
+ * It is the rail's own distance up from the bottom, then half its thickness, then
99
+ * the clearance the drawing keeps between two pieces of ink, then the deepest ink
100
+ * a node draws below its mark. The fitted area stops above the whole of it, so a
101
+ * lane whose mark lands on the bottom of that area puts its note in the gap this
102
+ * band left rather than on the rail's own stroke. It is arithmetic rather than a
103
+ * number because every term in it has its own reason, and retuning the rail or the
104
+ * type moves the fit with it rather than leaving the band behind.
54
105
  */
55
- function fit(nodes) {
56
- if (nodes.length === 0)
57
- return () => ({ x: CANVAS_WIDTH / 2, y: CANVAS_HEIGHT / 2 });
58
- const xs = nodes.map((node) => node.x);
59
- const ys = nodes.map((node) => node.y);
60
- const minX = Math.min(...xs);
61
- const minY = Math.min(...ys);
62
- const spanX = Math.max(...xs) - minX;
63
- const spanY = Math.max(...ys) - minY;
64
- const roomX = CANVAS_WIDTH - PADDING * 2;
65
- const roomY = CANVAS_HEIGHT - PADDING * 2;
66
- const scale = Math.min(spanX === 0 ? Number.POSITIVE_INFINITY : roomX / spanX, spanY === 0 ? Number.POSITIVE_INFINITY : roomY / spanY);
67
- const unit = Number.isFinite(scale) && scale > 0 ? scale : 1;
68
- const offsetX = PADDING + (roomX - spanX * unit) / 2 - minX * unit;
69
- const offsetY = PADDING + (roomY - spanY * unit) / 2 - minY * unit;
70
- return (node) => ({ x: node.x * unit + offsetX, y: node.y * unit + offsetY });
106
+ const RAIL_BAND = RAIL_OFFSET + RAIL_THICKNESS / 2 + INK_CLEARANCE + NODE_NOTE_DEPTH;
107
+ /**
108
+ * The labels this drawing prints under one node, which are what a stroke leaving it
109
+ * has to be trimmed against.
110
+ *
111
+ * Each label is its own obstacle rather than one box enclosing both, and that is the
112
+ * whole of the trim rule as it applies here: a node's name and note are both below
113
+ * its mark, so an edge leaving sideways runs along the mark's centre line and never
114
+ * enters either of them, while an edge leaving downward enters both. The arithmetic
115
+ * is `lib/figure`, which is where the shared answer lives; what is left here is the
116
+ * two labels this drawing prints and where it prints them.
117
+ */
118
+ function nodeLabels(node) {
119
+ const labels = [{ text: node.name, size: NODE_NAME_SIZE, offset: NODE_NAME_OFFSET }];
120
+ if (node.note !== undefined) {
121
+ labels.push({ text: node.note, size: NODE_NOTE_SIZE, offset: NODE_NOTE_OFFSET });
122
+ }
123
+ return labels;
124
+ }
125
+ /**
126
+ * The head a carrying edge grows, at the end of the stroke a reader can see.
127
+ *
128
+ * The head is part of the stroke rather than part of the target mark, so it is
129
+ * placed at the stroke's own end and not at an inset from the mark's centre. A
130
+ * head measured from the centre would sit inside the clearance the stroke stops at
131
+ * and the two would read as two marks with a gap between them rather than as one
132
+ * arrow. Measured from the stroke's own end the tip is one ink clearance from the
133
+ * mark it points at, which is the same gap every other end of every other connector
134
+ * on the rail keeps.
135
+ *
136
+ * This is the one piece of the drawing that earns its place by being static. An
137
+ * edge that carries something has to say so to a reader who has turned every
138
+ * animation off, and a dashed overlay read as direction only while the dashes
139
+ * were moving, so the claim vanished for exactly the reader who most needed it.
140
+ * A head does not move, so it does not need a cycle, and it is legible in a
141
+ * printed page. The rail's travelling marker is then free to be pure atmosphere
142
+ * rather than the only thing carrying the meaning.
143
+ */
144
+ function flowHead(line) {
145
+ const dx = line.to.x - line.from.x;
146
+ const dy = line.to.y - line.from.y;
147
+ const length = Math.hypot(dx, dy);
148
+ if (length === 0)
149
+ return null;
150
+ const ux = dx / length;
151
+ const uy = dy / length;
152
+ const head = 5;
153
+ return (_jsx("path", { "data-slot": "pulse-graph-flow", d: `M ${line.to.x - uy * head} ${line.to.y + ux * head} L ${line.to.x} ${line.to.y} L ${line.to.x + uy * head} ${line.to.y - ux * head}`, strokeWidth: 1.5, fill: "none", className: "stroke-brand-ink" }));
71
154
  }
72
155
  /**
73
156
  * The lanes present, in ascending order.
@@ -159,19 +242,40 @@ function stagger(lane) {
159
242
  * sentence is what a screen reader gets. Pass `decorative` when that sentence
160
243
  * already says what the drawing shows.
161
244
  *
162
- * Positions are the caller's. The Component fits and draws, and it does not lay
163
- * out a graph, so it cannot know that two labels will not collide. A relation
164
- * naming a node that is not in `nodes` is dropped and counted on the element.
245
+ * **No stroke crosses a label, and every connector still reaches the marks it
246
+ * joins.** An edge used to run from one mark's centre to the other, and a node's
247
+ * name is printed under its mark, so every edge heading downward struck through the
248
+ * name of the node it left. Each label a node prints is now its own obstacle rather
249
+ * than part of one box enclosing the whole node, and an edge is trimmed only against
250
+ * the labels it would actually enter. That direction is the whole of the rule: a
251
+ * name and a note both sit below their mark, so an edge leaving sideways runs along
252
+ * the mark's centre line and clears both, and it stops at the mark's own edge with
253
+ * the ink clearance between. Backing every edge off the widest label at its node
254
+ * instead detached all five connectors on a six-stage rail from the circles they
255
+ * connect, which is a drawing of a shape with no relations on it. The Component
256
+ * cannot know that two labels will not collide with one another, which is the
257
+ * caller's positions to settle, and it does know every mark's radius, both label
258
+ * offsets and every label's own width, because a monospaced label's width is its
259
+ * character count. The rail is held to
260
+ * the same clearance rather than to a fixed distance from the canvas bottom,
261
+ * because a note is the deepest ink a node draws and a lane whose mark lands low
262
+ * put it under the rail. `test/pulse-graph.test.tsx` asserts both halves as
263
+ * geometry rather than the presence of a class name.
264
+ *
265
+ * **No label is drawn below `text-xs`.** The drawing is wrapped in a container
266
+ * that scrolls sideways, and the drawing itself holds a floor of its own
267
+ * coordinate space, so a phone sees the whole graph at a legible size rather than
268
+ * the whole graph at six pixels. That is the same bargain `Table` strikes with
269
+ * seven columns, and it is a bargain rather than an accident because the
270
+ * alternative is a picture of a shape with nothing written on it.
271
+ *
272
+ * Positions are the caller's, and two labels crossing one another is still the
273
+ * caller's to settle. A relation naming a node that is not in `nodes` is dropped
274
+ * and counted on the element.
165
275
  */
166
276
  function PulseGraph({ nodes, relations = [], label, decorative = false, className, }) {
167
- const place = fit(nodes);
168
277
  const byId = new Map(nodes.map((node) => [node.id, node]));
169
278
  const lanes = lanesOf(nodes);
170
- const drawn = relations.flatMap((relation) => {
171
- const from = byId.get(relation.from);
172
- const to = byId.get(relation.to);
173
- return from && to ? [{ relation, from: place(from), to: place(to) }] : [];
174
- });
175
279
  /**
176
280
  * The rail's two ends, taken from the lanes that exist rather than from the
177
281
  * canvas. A rail as wide as the canvas regardless of where the nodes are would
@@ -179,6 +283,27 @@ function PulseGraph({ nodes, relations = [], label, decorative = false, classNam
179
283
  * about a caller's data that the caller did not make.
180
284
  */
181
285
  const railNodes = nodes.filter((node) => node.lane !== undefined);
286
+ // A lane's note is the deepest ink a node draws, and the rail sits below the
287
+ // fitted area, so the fit stops above the band the rail reserves. A field has
288
+ // no rail, nothing reserves the foot, and the fit is the same band the top and
289
+ // the two sides get.
290
+ const place = fitPoints(nodes, {
291
+ width: CANVAS_WIDTH,
292
+ height: CANVAS_HEIGHT,
293
+ padding: PADDING,
294
+ bottom: railNodes.length ? CANVAS_HEIGHT - RAIL_BAND : undefined,
295
+ });
296
+ const drawn = relations.flatMap((relation) => {
297
+ const from = byId.get(relation.from);
298
+ const to = byId.get(relation.to);
299
+ if (!from || !to)
300
+ return [];
301
+ // Each end carries its mark's centre and the labels printed under it, because
302
+ // a stroke is trimmed against the labels it would enter and the two are not the
303
+ // same thing.
304
+ const end = (node) => ({ at: place(node), labels: nodeLabels(node) });
305
+ return [{ relation, from: end(from), to: end(to) }];
306
+ });
182
307
  const railAt = railNodes.length
183
308
  ? {
184
309
  x0: Math.min(...railNodes.map((node) => place(node).x)) - NODE_RADIUS - 10,
@@ -186,27 +311,16 @@ function PulseGraph({ nodes, relations = [], label, decorative = false, classNam
186
311
  }
187
312
  : null;
188
313
  const railY = CANVAS_HEIGHT - RAIL_OFFSET;
189
- return (_jsxs("svg", { "data-slot": "pulse-graph", "data-unresolved-relations": relations.length - drawn.length, "data-lanes": lanes.length, viewBox: `0 0 ${CANVAS_WIDTH} ${CANVAS_HEIGHT}`, xmlns: "http://www.w3.org/2000/svg", role: decorative ? undefined : 'img', "aria-label": decorative ? undefined : label, "aria-hidden": decorative || undefined, className: cn('h-auto w-full', className), children: [railAt ? (_jsxs("g", { "data-slot": "pulse-graph-rail", children: [_jsx("line", { "data-slot": "pulse-graph-rail-line", x1: railAt.x0, y1: railY, x2: railAt.x1, y2: railY, strokeWidth: RAIL_THICKNESS, className: "stroke-border" }), _jsx("g", { "data-slot": "pulse-graph-marker", className: "prism-ambient-travel prism-ambient-delay-1", style: { '--ambient-travel-distance': `${railAt.x1 - railAt.x0}px` }, children: _jsx("rect", { x: railAt.x0 - 2.5, y: railY - 2.5, width: 5, height: 5, rx: 2.5, className: "fill-brand-ink" }) })] })) : null, drawn.map(({ relation, from, to }) => (_jsxs("g", { "data-slot": "pulse-graph-relation", "data-relation": `${relation.from}-${relation.to}`, "data-carries": relation.carries || undefined, children: [_jsx("line", { x1: from.x, y1: from.y, x2: to.x, y2: to.y, strokeWidth: 1, strokeDasharray: relation.indirect ? '4 4' : undefined, className: "stroke-muted-foreground/50 fill-none" }), relation.carries
190
- ? (() => {
191
- const dx = to.x - from.x;
192
- const dy = to.y - from.y;
193
- const length = Math.hypot(dx, dy);
194
- if (length === 0)
195
- return null;
196
- // The head sits back from the target mark so it never overlaps
197
- // it, and is scaled to the line's own length so a short edge and
198
- // a long one get the same size head rather than a stretched one.
199
- const inset = NODE_RADIUS + 4;
200
- const ux = dx / length;
201
- const uy = dy / length;
202
- const hx = to.x - ux * inset;
203
- const hy = to.y - uy * inset;
204
- const head = 5;
205
- return (_jsx("path", { "data-slot": "pulse-graph-flow", d: `M ${hx - uy * head} ${hy + ux * head} L ${hx} ${hy} L ${hx + uy * head} ${hy - ux * head}`, strokeWidth: 1.5, fill: "none", className: "stroke-brand-ink" }));
206
- })()
207
- : null] }, `${relation.from}-${relation.to}`))), nodes.map((node) => {
208
- const at = place(node);
209
- return (_jsxs("g", { "data-slot": "pulse-graph-node", "data-node": node.id, "data-emphasis": node.emphasis || undefined, "data-lane": node.lane, children: [node.emphasis ? (_jsx("circle", { "data-slot": "pulse-graph-halo", cx: at.x, cy: at.y, r: NODE_RADIUS, strokeWidth: 0, className: cn('fill-brand-ink/25', 'prism-ambient-pulse', stagger(node.lane ?? 0)) })) : null, _jsx("circle", { cx: at.x, cy: at.y, r: NODE_RADIUS, strokeWidth: node.emphasis ? 2 : 1, className: cn(node.emphasis ? 'fill-accent stroke-brand-ink' : 'fill-card stroke-border') }), _jsx("text", { x: at.x, y: at.y + NODE_NAME_OFFSET, fontSize: NODE_NAME_SIZE, textAnchor: "middle", className: "fill-foreground font-mono", children: node.name }), node.note ? (_jsx("text", { x: at.x, y: at.y + NODE_NOTE_OFFSET, fontSize: NODE_NOTE_SIZE, textAnchor: "middle", className: "fill-muted-foreground font-mono", children: node.note })) : null] }, node.id));
210
- })] }));
314
+ return (
315
+ // The sideways region, for the reason `LEGIBLE_MIN_WIDTH` states: the drawing
316
+ // holds a legible floor and a container narrower than it moves under the
317
+ // drawing rather than the drawing shrinking below what it says.
318
+ _jsx("div", { "data-slot": "pulse-graph-container", className: "w-full overflow-x-auto", children: _jsxs("svg", { "data-slot": "pulse-graph", "data-unresolved-relations": relations.length - drawn.length, "data-lanes": lanes.length, viewBox: `0 0 ${CANVAS_WIDTH} ${CANVAS_HEIGHT}`, xmlns: "http://www.w3.org/2000/svg", role: decorative ? undefined : 'img', "aria-label": decorative ? undefined : label, "aria-hidden": decorative || undefined, className: cn('h-auto w-full', LEGIBLE_MIN_WIDTH, className), children: [railAt ? (_jsxs("g", { "data-slot": "pulse-graph-rail", children: [_jsx("line", { "data-slot": "pulse-graph-rail-line", x1: railAt.x0, y1: railY, x2: railAt.x1, y2: railY, strokeWidth: RAIL_THICKNESS, className: "stroke-border" }), _jsx("g", { "data-slot": "pulse-graph-marker", className: "prism-ambient-travel prism-ambient-delay-1", style: { '--ambient-travel-distance': `${railAt.x1 - railAt.x0}px` }, children: _jsx("rect", { x: railAt.x0 - 2.5, y: railY - 2.5, width: 5, height: 5, rx: 2.5, className: "fill-brand-ink" }) })] })) : null, drawn.map(({ relation, from, to }) => {
319
+ const line = strokeBetween(from.at, to.at, NODE_RADIUS, from.labels, to.labels);
320
+ return (_jsxs("g", { "data-slot": "pulse-graph-relation", "data-relation": `${relation.from}-${relation.to}`, "data-carries": relation.carries || undefined, children: [_jsx("line", { x1: line.from.x, y1: line.from.y, x2: line.to.x, y2: line.to.y, strokeWidth: 1, strokeDasharray: relation.indirect ? '4 4' : undefined, className: "stroke-muted-foreground/50 fill-none" }), relation.carries ? flowHead(line) : null] }, `${relation.from}-${relation.to}`));
321
+ }), nodes.map((node) => {
322
+ const at = place(node);
323
+ return (_jsxs("g", { "data-slot": "pulse-graph-node", "data-node": node.id, "data-emphasis": node.emphasis || undefined, "data-lane": node.lane, children: [node.emphasis ? (_jsx("circle", { "data-slot": "pulse-graph-halo", cx: at.x, cy: at.y, r: NODE_RADIUS, strokeWidth: 0, className: cn('fill-brand-ink/25', 'prism-ambient-pulse', stagger(node.lane ?? 0)) })) : null, _jsx("circle", { cx: at.x, cy: at.y, r: NODE_RADIUS, strokeWidth: node.emphasis ? 2 : 1, className: cn(node.emphasis ? 'fill-accent stroke-brand-ink' : 'fill-card stroke-border') }), _jsx("text", { x: at.x, y: at.y + NODE_NAME_OFFSET, fontSize: NODE_NAME_SIZE, textAnchor: "middle", className: "fill-foreground font-mono", children: node.name }), node.note ? (_jsx("text", { "data-slot": "pulse-graph-node-note", x: at.x, y: at.y + NODE_NOTE_OFFSET, fontSize: NODE_NOTE_SIZE, textAnchor: "middle", className: "fill-muted-foreground font-mono", children: node.note })) : null] }, node.id));
324
+ })] }) }));
211
325
  }
212
326
  export { PulseGraph };
@@ -102,45 +102,47 @@ export declare function headingSizeClass(level: HeadingLevel): string;
102
102
  *
103
103
  * | level | step | authored value | at `sm` | authored value |
104
104
  * | --- | --- | --- | --- | --- |
105
- * | `h1` | `text-3xl` | 1.875rem / 1.2 | `sm:text-4xl` | 2.25rem / 1.111 |
106
- * | `h2` | `text-2xl` | 1.5rem / 1.333 | `sm:text-3xl` | 1.875rem / 1.2 |
107
- * | `h3` | `text-xl` | 1.25rem / 1.4 | `sm:text-2xl` | 1.5rem / 1.333 |
108
- * | `h4` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
109
- * | `h5` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
105
+ * | `h1` | `text-5xl` | 3rem / 1 | `sm:text-6xl` | 3.75rem / 1 |
106
+ * | `h2` | `text-4xl` | 2.25rem / 1.111 | `sm:text-5xl` | 3rem / 1 |
107
+ * | `h3` | `text-3xl` | 1.875rem / 1.2 | `sm:text-4xl` | 2.25rem / 1.111 |
108
+ * | `h4` | `text-2xl` | 1.5rem / 1.333 | `sm:text-3xl` | 1.875rem / 1.2 |
109
+ * | `h5` | `text-xl` | 1.25rem / 1.4 | `sm:text-2xl` | 1.5rem / 1.333 |
110
110
  * | `h6` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
111
111
  *
112
- * **`h1` is Display and stays Display**, because that is the role `DESIGN.md`
113
- * gives it: a page's own heading is the largest thing on the page. The step above
114
- * it does not exist and this table does not invent one, so "nothing in the system
115
- * goes above `4xl`" is untouched and the authored scale in `packages/tokens` does
116
- * not change.
112
+ * **`h1` is Display and Display is the ceiling of the scale**, because that is
113
+ * the role `DESIGN.md` gives it: a page's own heading is the largest thing on the
114
+ * page. The two steps above `4xl` are authored rather than invented here, and they
115
+ * are authored as a pair rather than as one step, because every rung of this table
116
+ * is a pair: a base step and one step up at `sm`. A single new step would have made
117
+ * Display the one heading in the system that does not grow, which is a smaller
118
+ * defect than the one this table was written to remove and is still a defect.
119
+ * `5xl` is `4xl` times four thirds, which is the first gap in the group wider than
120
+ * any gap it already had, and `6xl` is `5xl` times five fourths. Nothing in the
121
+ * system goes above `6xl`, and no heading level steps above Display.
117
122
  *
118
123
  * **`h2` is one step below it, and `h2` is what almost every Block renders**,
119
124
  * because a Block is composed under a heading the document already owns. That is
120
- * the whole of the change a consumer sees: a section title moves from 1.875rem to
121
- * 1.5rem, and above `sm` from 2.25rem to 1.875rem.
125
+ * the whole of what a consumer sees: a section title moves from 1.5rem to
126
+ * 2.25rem, and above `sm` from 1.875rem to 3rem.
122
127
  *
123
- * **The floor is `h4`, and the floor is `text-lg`.** `lg` is the deepest authored
128
+ * **The floor is `h6`, and the floor is `text-lg`.** `lg` is the deepest authored
124
129
  * step that is not smaller than Body, and Body is 400 at 1.125rem, so a heading
125
130
  * that stepped one further would render smaller than the copy it introduces and
126
- * read as a caption rather than as a heading. `h5` and `h6` hold at that step and
127
- * do not wrap: `childLevel()` already clamps at `h6` for the same reason, and a
128
- * repeated size in a document that has nested a Block three deep is the cheaper
129
- * trade against a heading smaller than its own body. A heading at the floor is
130
- * still a heading: it keeps `font-semibold`, keeps `tracking-tight`, and keeps
131
- * `text-balance` at every step, so weight and tracking tell it apart from body
132
- * copy where size no longer can.
131
+ * read as a caption rather than as a heading. The table has six rows and the scale
132
+ * has exactly six steps at or above Body, so the ladder lands on the floor rather
133
+ * than running off the bottom of it: every level now holds a step of its own, where
134
+ * before `h4` through `h6` shared one. `childLevel()` clamps at `h6` for the same
135
+ * reason the table stops there. A heading at the floor is still a heading: it keeps
136
+ * `font-semibold`, keeps `tracking-tight`, and keeps `text-balance` at every step,
137
+ * so weight and tracking tell it apart from body copy where size no longer can.
133
138
  *
134
139
  * So a consumer gets the hierarchy by passing the level it was already passing,
135
140
  * and a Block moved from an `h2` section into an `h3` one carries its size with
136
141
  * it, which is what `headingLevel` was introduced to do.
137
142
  *
138
- * **`Prose` already walked this ladder**, at the same steps and stopping at the
139
- * same `h4`, so a document rendered through `Prose` and a page composed from
140
- * Blocks now read as one hierarchy rather than as two answers to one question.
141
- * This Component was the second answer, and it was the one every catalogue Item
142
- * used, which is why the disagreement was visible on every product page and not
143
- * on a documentation page.
143
+ * **`Prose` walks this ladder from one rung down**, because its measure is the
144
+ * reading measure rather than the page column and the Display step does not fit a
145
+ * 42rem column. See `Prose`.
144
146
  */
145
147
  export declare function SectionHeading({ index, eyebrow, title, as: Heading, description, align, id, className, }: {
146
148
  /**
@@ -168,11 +170,11 @@ export declare function SectionHeading({ index, eyebrow, title, as: Heading, des
168
170
  * entry point.
169
171
  *
170
172
  * It also picks the size, and there is no prop for the size. The table is in
171
- * this Component's JSDoc above: `h1` at 1.875rem, `h2` at 1.5rem, `h3` at
172
- * 1.25rem, and `h4` through `h6` at 1.125rem, each stepping up one authored
173
- * step above `sm`. Passing a deeper level is therefore a typographic decision
174
- * as well as an outline one, which is the point: a Block composed one level
175
- * deeper should read as one level deeper.
173
+ * this Component's JSDoc above: `h1` at 3rem, `h2` at 2.25rem, `h3` at
174
+ * 1.875rem, `h4` at 1.5rem, `h5` at 1.25rem and `h6` at 1.125rem, each
175
+ * stepping up one authored step above `sm`. Passing a deeper level is
176
+ * therefore a typographic decision as well as an outline one, which is the
177
+ * point: a Block composed one level deeper should read as one level deeper.
176
178
  */
177
179
  as?: HeadingLevel;
178
180
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/components/ui/section.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,+BAWjC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAKlE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAG5D;AAoBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAE5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,KAAK,EACL,EAAE,EAAE,OAAc,EAClB,WAAW,EACX,KAAgB,EAChB,EAAE,EACF,SAAS,GACV,EAAE;IACD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,SAAS,CAAA;IAChB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IACzB;;;;;;;;;;;;OAYG;IACH,EAAE,CAAC,EAAE,YAAY,CAAA;IACjB;;;;;;;;;;;OAWG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BAgCA"}
1
+ {"version":3,"file":"section.d.ts","sourceRoot":"","sources":["../../../src/components/ui/section.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,EACtB,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,+BAWjC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAKlE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,GAAG,YAAY,CAG5D;AAoBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAE5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,OAAO,EACP,KAAK,EACL,EAAE,EAAE,OAAc,EAClB,WAAW,EACX,KAAgB,EAChB,EAAE,EACF,SAAS,GACV,EAAE;IACD;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB,KAAK,EAAE,SAAS,CAAA;IAChB,WAAW,CAAC,EAAE,SAAS,CAAA;IACvB,KAAK,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IACzB;;;;;;;;;;;;OAYG;IACH,EAAE,CAAC,EAAE,YAAY,CAAA;IACjB;;;;;;;;;;;OAWG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,+BAgCA"}
@@ -63,11 +63,11 @@ export function childLevel(level) {
63
63
  * two copies in step.
64
64
  */
65
65
  const HEADING_SIZE = {
66
- h1: 'text-3xl sm:text-4xl',
67
- h2: 'text-2xl sm:text-3xl',
68
- h3: 'text-xl sm:text-2xl',
69
- h4: 'text-lg sm:text-xl',
70
- h5: 'text-lg sm:text-xl',
66
+ h1: 'text-5xl sm:text-6xl',
67
+ h2: 'text-4xl sm:text-5xl',
68
+ h3: 'text-3xl sm:text-4xl',
69
+ h4: 'text-2xl sm:text-3xl',
70
+ h5: 'text-xl sm:text-2xl',
71
71
  h6: 'text-lg sm:text-xl',
72
72
  };
73
73
  /**
@@ -113,45 +113,47 @@ export function headingSizeClass(level) {
113
113
  *
114
114
  * | level | step | authored value | at `sm` | authored value |
115
115
  * | --- | --- | --- | --- | --- |
116
- * | `h1` | `text-3xl` | 1.875rem / 1.2 | `sm:text-4xl` | 2.25rem / 1.111 |
117
- * | `h2` | `text-2xl` | 1.5rem / 1.333 | `sm:text-3xl` | 1.875rem / 1.2 |
118
- * | `h3` | `text-xl` | 1.25rem / 1.4 | `sm:text-2xl` | 1.5rem / 1.333 |
119
- * | `h4` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
120
- * | `h5` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
116
+ * | `h1` | `text-5xl` | 3rem / 1 | `sm:text-6xl` | 3.75rem / 1 |
117
+ * | `h2` | `text-4xl` | 2.25rem / 1.111 | `sm:text-5xl` | 3rem / 1 |
118
+ * | `h3` | `text-3xl` | 1.875rem / 1.2 | `sm:text-4xl` | 2.25rem / 1.111 |
119
+ * | `h4` | `text-2xl` | 1.5rem / 1.333 | `sm:text-3xl` | 1.875rem / 1.2 |
120
+ * | `h5` | `text-xl` | 1.25rem / 1.4 | `sm:text-2xl` | 1.5rem / 1.333 |
121
121
  * | `h6` | `text-lg` | 1.125rem / 1.556 | `sm:text-xl` | 1.25rem / 1.4 |
122
122
  *
123
- * **`h1` is Display and stays Display**, because that is the role `DESIGN.md`
124
- * gives it: a page's own heading is the largest thing on the page. The step above
125
- * it does not exist and this table does not invent one, so "nothing in the system
126
- * goes above `4xl`" is untouched and the authored scale in `packages/tokens` does
127
- * not change.
123
+ * **`h1` is Display and Display is the ceiling of the scale**, because that is
124
+ * the role `DESIGN.md` gives it: a page's own heading is the largest thing on the
125
+ * page. The two steps above `4xl` are authored rather than invented here, and they
126
+ * are authored as a pair rather than as one step, because every rung of this table
127
+ * is a pair: a base step and one step up at `sm`. A single new step would have made
128
+ * Display the one heading in the system that does not grow, which is a smaller
129
+ * defect than the one this table was written to remove and is still a defect.
130
+ * `5xl` is `4xl` times four thirds, which is the first gap in the group wider than
131
+ * any gap it already had, and `6xl` is `5xl` times five fourths. Nothing in the
132
+ * system goes above `6xl`, and no heading level steps above Display.
128
133
  *
129
134
  * **`h2` is one step below it, and `h2` is what almost every Block renders**,
130
135
  * because a Block is composed under a heading the document already owns. That is
131
- * the whole of the change a consumer sees: a section title moves from 1.875rem to
132
- * 1.5rem, and above `sm` from 2.25rem to 1.875rem.
136
+ * the whole of what a consumer sees: a section title moves from 1.5rem to
137
+ * 2.25rem, and above `sm` from 1.875rem to 3rem.
133
138
  *
134
- * **The floor is `h4`, and the floor is `text-lg`.** `lg` is the deepest authored
139
+ * **The floor is `h6`, and the floor is `text-lg`.** `lg` is the deepest authored
135
140
  * step that is not smaller than Body, and Body is 400 at 1.125rem, so a heading
136
141
  * that stepped one further would render smaller than the copy it introduces and
137
- * read as a caption rather than as a heading. `h5` and `h6` hold at that step and
138
- * do not wrap: `childLevel()` already clamps at `h6` for the same reason, and a
139
- * repeated size in a document that has nested a Block three deep is the cheaper
140
- * trade against a heading smaller than its own body. A heading at the floor is
141
- * still a heading: it keeps `font-semibold`, keeps `tracking-tight`, and keeps
142
- * `text-balance` at every step, so weight and tracking tell it apart from body
143
- * copy where size no longer can.
142
+ * read as a caption rather than as a heading. The table has six rows and the scale
143
+ * has exactly six steps at or above Body, so the ladder lands on the floor rather
144
+ * than running off the bottom of it: every level now holds a step of its own, where
145
+ * before `h4` through `h6` shared one. `childLevel()` clamps at `h6` for the same
146
+ * reason the table stops there. A heading at the floor is still a heading: it keeps
147
+ * `font-semibold`, keeps `tracking-tight`, and keeps `text-balance` at every step,
148
+ * so weight and tracking tell it apart from body copy where size no longer can.
144
149
  *
145
150
  * So a consumer gets the hierarchy by passing the level it was already passing,
146
151
  * and a Block moved from an `h2` section into an `h3` one carries its size with
147
152
  * it, which is what `headingLevel` was introduced to do.
148
153
  *
149
- * **`Prose` already walked this ladder**, at the same steps and stopping at the
150
- * same `h4`, so a document rendered through `Prose` and a page composed from
151
- * Blocks now read as one hierarchy rather than as two answers to one question.
152
- * This Component was the second answer, and it was the one every catalogue Item
153
- * used, which is why the disagreement was visible on every product page and not
154
- * on a documentation page.
154
+ * **`Prose` walks this ladder from one rung down**, because its measure is the
155
+ * reading measure rather than the page column and the Display step does not fit a
156
+ * 42rem column. See `Prose`.
155
157
  */
156
158
  export function SectionHeading({ index, eyebrow, title, as: Heading = 'h2', description, align = 'center', id, className, }) {
157
159
  return (_jsxs("div", { className: cn('flex flex-col gap-4', align === 'center' ? 'items-center text-center' : 'items-start text-left', className), children: [index ? (_jsx("span", { className: "text-muted-foreground font-mono text-xs font-normal tracking-normal", children: index })) : null, eyebrow ? (_jsx("span", { className: "text-muted-foreground text-sm font-medium tracking-wide uppercase", children: eyebrow })) : null, _jsx(Heading, { id: id, className: cn('font-semibold tracking-tight text-balance', headingSizeClass(Heading)), children: title }), description ? (_jsx("p", { className: "text-muted-foreground max-w-measure text-lg text-pretty", children: description })) : null] }));