@caqophony/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.
package/dist/material.js CHANGED
@@ -1,15 +1,24 @@
1
1
  'use client';
2
2
  import {
3
3
  IntentBadge
4
- } from "./chunk-Y6LCXEXZ.js";
4
+ } from "./chunk-44GEHNZP.js";
5
5
  import {
6
+ CONTENT_GUTTER,
6
7
  Icon,
7
8
  ScrollHintXMark
8
- } from "./chunk-BAW3RIMF.js";
9
+ } from "./chunk-ID7VBGJA.js";
9
10
  import {
10
11
  cn
11
12
  } from "./chunk-DP5WQAPO.js";
12
13
 
14
+ // src/material/sky.tsx
15
+ import {
16
+ Children,
17
+ createContext,
18
+ isValidElement,
19
+ useContext
20
+ } from "react";
21
+
13
22
  // src/material/tones.ts
14
23
  var FILL = {
15
24
  "night-0": "fill-night-0",
@@ -72,69 +81,261 @@ var BG = {
72
81
  page: "bg-background"
73
82
  };
74
83
 
84
+ // src/material/art.tsx
85
+ import { Fragment, jsx } from "react/jsx-runtime";
86
+ var ANCHOR = {
87
+ top: "xMidYMin",
88
+ center: "xMidYMid",
89
+ bottom: "xMidYMax",
90
+ right: "xMaxYMid"
91
+ };
92
+ var preserveOf = (anchor, fit = "cover") => `${ANCHOR[anchor]} ${fit === "contain" ? "meet" : "slice"}`;
93
+ var SHOWN = {
94
+ phone: "size-full md:hidden",
95
+ desktop: "hidden size-full md:block",
96
+ stack: "size-full lg:hidden",
97
+ side: "hidden size-full lg:block"
98
+ };
99
+ var frameClass = (only) => only ? SHOWN[only] : "size-full";
100
+
101
+ // src/material/scene-measures.ts
102
+ var FIELD_UNITS = "field units";
103
+ var WIDTH = { of: "width", times: 1 };
104
+ var HEIGHT = { of: "height", times: 1 };
105
+ var ACROSS = {
106
+ unit: FIELD_UNITS,
107
+ whole: false,
108
+ min: 0,
109
+ minOpen: false,
110
+ max: WIDTH
111
+ };
112
+ var DOWN = {
113
+ unit: FIELD_UNITS,
114
+ whole: false,
115
+ min: 0,
116
+ minOpen: false,
117
+ max: HEIGHT
118
+ };
119
+ var SEED = {
120
+ unit: "a seed: the same one draws the same shape",
121
+ whole: true,
122
+ min: 0,
123
+ minOpen: false,
124
+ max: 2147483646
125
+ };
126
+ var SCENE_MEASURES = {
127
+ SkyField: {
128
+ width: {
129
+ unit: "field units, the frame's own coordinates",
130
+ whole: false,
131
+ min: 0,
132
+ minOpen: true,
133
+ max: null
134
+ },
135
+ height: {
136
+ unit: "field units, the frame's own coordinates",
137
+ whole: false,
138
+ min: 0,
139
+ minOpen: true,
140
+ max: null
141
+ }
142
+ },
143
+ Halo: {
144
+ cx: ACROSS,
145
+ cy: DOWN,
146
+ "rings.r": {
147
+ unit: FIELD_UNITS,
148
+ whole: false,
149
+ min: 0,
150
+ minOpen: true,
151
+ max: WIDTH
152
+ }
153
+ },
154
+ CloudBank: {
155
+ y: DOWN,
156
+ /** Below 0.1 a radius rounds to 0.0 in the path's one decimal. */
157
+ min: {
158
+ unit: FIELD_UNITS,
159
+ whole: false,
160
+ min: 0.1,
161
+ minOpen: false,
162
+ max: HEIGHT
163
+ },
164
+ max: {
165
+ unit: FIELD_UNITS,
166
+ whole: false,
167
+ min: 0.1,
168
+ minOpen: false,
169
+ max: HEIGHT
170
+ },
171
+ climb: {
172
+ unit: FIELD_UNITS,
173
+ whole: false,
174
+ min: { of: "height", times: -1 },
175
+ minOpen: false,
176
+ max: HEIGHT
177
+ },
178
+ seed: SEED
179
+ },
180
+ Ridge: {
181
+ y: DOWN,
182
+ rise: DOWN,
183
+ swells: {
184
+ unit: "swells across the field",
185
+ whole: true,
186
+ min: 1,
187
+ minOpen: false,
188
+ max: 512
189
+ },
190
+ seed: SEED
191
+ },
192
+ Stars: {
193
+ depth: {
194
+ unit: "a fraction of the field's height",
195
+ whole: false,
196
+ min: 0,
197
+ minOpen: true,
198
+ max: 1
199
+ },
200
+ count: {
201
+ unit: "stars",
202
+ whole: true,
203
+ min: 0,
204
+ minOpen: false,
205
+ max: 5e3
206
+ },
207
+ seed: SEED
208
+ },
209
+ RouteLine: {
210
+ "points.x": ACROSS,
211
+ "points.y": DOWN,
212
+ labelSize: {
213
+ unit: FIELD_UNITS,
214
+ whole: false,
215
+ min: 0,
216
+ minOpen: true,
217
+ max: HEIGHT
218
+ }
219
+ }
220
+ };
221
+ var boundText = (b) => typeof b === "number" ? String(b) : b.times === 1 ? `the field's ${b.of}` : b.times === -1 ? `minus the field's ${b.of}` : `${b.times} x the field's ${b.of}`;
222
+ function measureText(m) {
223
+ const low = boundText(m.min);
224
+ const range = m.max === null ? `${m.minOpen ? "above" : "at least"} ${low}` : m.minOpen ? `above ${low}, at most ${boundText(m.max)}` : `from ${low} to ${boundText(m.max)}`;
225
+ return `${m.unit}: ${m.whole ? `a whole number ${range}` : range}`;
226
+ }
227
+ var boundIn = (b, field) => {
228
+ if (typeof b === "number") return b;
229
+ if (field === null)
230
+ throw new Error(
231
+ `a range bound of the field's ${b.of} needs a field to read it in`
232
+ );
233
+ return field[b.of] * b.times;
234
+ };
235
+ function measured(piece, key, name, value, field) {
236
+ const table = SCENE_MEASURES[piece];
237
+ const m = table[key];
238
+ if (m === void 0)
239
+ throw new Error(`${piece}: \`${key}\` has no measure in SCENE_MEASURES`);
240
+ const low = boundIn(m.min, field);
241
+ const high = m.max === null ? Number.POSITIVE_INFINITY : boundIn(m.max, field);
242
+ const inside = Number.isFinite(value) && (m.minOpen ? value > low : value >= low) && value <= high && (!m.whole || Number.isInteger(value));
243
+ if (!inside)
244
+ throw new Error(
245
+ `${piece}: \`${name}\` is ${measureText(m)} (got ${value})`
246
+ );
247
+ }
248
+
75
249
  // src/material/sky.tsx
76
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
77
- function finite(piece, facts) {
250
+ import { Fragment as Fragment2, jsx as jsx2, jsxs } from "react/jsx-runtime";
251
+ function measuredAll(piece, field, facts) {
78
252
  for (const [name, value] of Object.entries(facts))
79
- if (!Number.isFinite(value))
80
- throw new Error(
81
- `${piece}: \`${name}\` must be a finite number (got ${value})`
82
- );
253
+ if (value !== void 0) measured(piece, name, name, value, field);
83
254
  }
84
- function aboveZero(piece, facts) {
85
- for (const [name, value] of Object.entries(facts))
255
+ function sized(piece, field) {
256
+ for (const [name, value] of Object.entries({
257
+ width: field.width,
258
+ height: field.height
259
+ }))
86
260
  if (!Number.isFinite(value) || value <= 0)
87
261
  throw new Error(
88
262
  `${piece}: \`${name}\` must be a finite number above 0 (got ${value})`
89
263
  );
90
264
  }
91
- function wholeWithin(piece, name, value, [low, high]) {
92
- if (!Number.isInteger(value) || value < low || value > high)
265
+ var MAX_BUMPS = 2e3;
266
+ var BLEED_BOTH_SIDES = {
267
+ left: "bleed",
268
+ right: "bleed",
269
+ down: false
270
+ };
271
+ var SceneCopyContext = createContext(null);
272
+ var GroundContext = createContext(null);
273
+ var FieldContext = createContext(null);
274
+ var LayerContext = createContext("pieces");
275
+ function useField(piece) {
276
+ const field = useContext(FieldContext);
277
+ if (field === null)
93
278
  throw new Error(
94
- `${piece}: \`${name}\` must be a whole number from ${low} to ${high} (got ${value})`
279
+ `${piece}: a scene piece draws inside a SkyField, which gives it the field's size; this one has no SkyField around it`
95
280
  );
281
+ return field;
282
+ }
283
+ function reachOf(copy, only) {
284
+ if (copy === null) return BLEED_BOTH_SIDES;
285
+ if (only === "stack" || only === "phone")
286
+ return { left: "bleed", right: "bleed", down: true };
287
+ return copy === "start" ? { left: "close", right: "bleed", down: true } : { left: "bleed", right: "close", down: true };
96
288
  }
97
- var MIN_BUMP_RADIUS = 0.1;
98
- var MAX_BUMPS = 2e3;
99
- var MAX_SWELLS = 512;
100
- var MAX_STARS = 5e3;
101
- var ANCHOR = {
102
- top: "xMidYMin slice",
103
- center: "xMidYMid slice",
104
- bottom: "xMidYMax slice",
105
- right: "xMaxYMid slice"
106
- };
107
289
  function SkyField({
108
290
  width,
109
291
  height,
110
292
  anchor,
293
+ fit,
111
294
  only,
112
295
  ground,
113
296
  children
114
297
  }) {
115
- aboveZero("SkyField", { width, height });
298
+ measuredAll("SkyField", null, { width, height });
299
+ const copy = useContext(SceneCopyContext);
300
+ const under = useContext(GroundContext);
301
+ const routes = Children.toArray(children).filter(
302
+ (c) => isValidElement(c) && c.type === RouteLine
303
+ );
304
+ const field = {
305
+ width,
306
+ height,
307
+ reach: reachOf(copy, only),
308
+ ground: ground === "none" ? under : ground,
309
+ routes: new Set(
310
+ routes.map(
311
+ (r) => isValidElement(r) ? r.props.points : r
312
+ )
313
+ )
314
+ };
116
315
  return /* @__PURE__ */ jsxs(
117
316
  "svg",
118
317
  {
119
318
  viewBox: `0 0 ${width} ${height}`,
120
- preserveAspectRatio: ANCHOR[anchor],
319
+ preserveAspectRatio: preserveOf(anchor, fit),
121
320
  "aria-hidden": "true",
122
- className: cn(
123
- "absolute inset-0 size-full",
124
- only === "phone" && "md:hidden",
125
- only === "desktop" && "hidden md:block"
126
- ),
321
+ className: cn("absolute inset-0", frameClass(only)),
127
322
  children: [
128
- /* @__PURE__ */ jsx("rect", { width, height, className: FILL[ground] }),
129
- children
323
+ ground !== "none" && /* @__PURE__ */ jsx2("rect", { width, height, className: FILL[ground] }),
324
+ /* @__PURE__ */ jsx2(GroundContext.Provider, { value: field.ground, children: /* @__PURE__ */ jsxs(FieldContext.Provider, { value: field, children: [
325
+ children,
326
+ routes.length > 0 && /* @__PURE__ */ jsx2("g", { "data-route-labels": "", children: /* @__PURE__ */ jsx2(LayerContext.Provider, { value: "labels", children: routes }) })
327
+ ] }) })
130
328
  ]
131
329
  }
132
330
  );
133
331
  }
134
332
  function Halo({ cx, cy, rings }) {
135
- finite("Halo", { cx, cy });
136
- rings.forEach((ring, i) => aboveZero("Halo", { [`rings[${i}].r`]: ring.r }));
137
- return /* @__PURE__ */ jsx(Fragment, { children: rings.map((ring, i) => /* @__PURE__ */ jsx(
333
+ const field = useField("Halo");
334
+ measuredAll("Halo", field, { cx, cy });
335
+ rings.forEach(
336
+ (ring, i) => measured("Halo", "rings.r", `rings[${i}].r`, ring.r, field)
337
+ );
338
+ return /* @__PURE__ */ jsx2(Fragment2, { children: rings.map((ring, i) => /* @__PURE__ */ jsx2(
138
339
  "circle",
139
340
  {
140
341
  cx,
@@ -154,101 +355,219 @@ function rng(seed) {
154
355
  return (s - 1) / (RNG_MODULUS - 1);
155
356
  };
156
357
  }
157
- function CloudBank({
158
- width,
159
- height,
160
- y,
161
- min,
162
- max,
163
- climb = 0,
164
- level = false,
165
- seed,
166
- tone
167
- }) {
168
- aboveZero("CloudBank", { width, height });
169
- finite("CloudBank", { y, min, max, climb, seed });
170
- if (min <= 0 || max < min)
358
+ var BLEED_STREAM = 7919;
359
+ var BLEED_WIDTHS = 2;
360
+ var DOWN_SHARE = 0.5;
361
+ function cloudBankPaths(field, bank, reach) {
362
+ const { y, min, max, climb = 0, level = false, seed } = bank;
363
+ const { width, height } = field;
364
+ sized("CloudBank", field);
365
+ measuredAll("CloudBank", field, { y, min, max, climb, seed });
366
+ if (max < min)
171
367
  throw new Error(
172
- `CloudBank: the bump radius range must have min > 0 and max >= min (got min ${min}, max ${max}); a zero radius never reaches the field's edge`
368
+ `CloudBank: the bump radius range must have max >= min (got min ${min}, max ${max})`
173
369
  );
174
- if (min < MIN_BUMP_RADIUS)
175
- throw new Error(
176
- `CloudBank: \`min\` must be at least ${MIN_BUMP_RADIUS} (got ${min}); a smaller radius rounds to nothing in the path`
177
- );
178
- const bumps = Math.ceil((width + 2 * max) / (2 * min));
370
+ const bumps = Math.ceil(
371
+ ((1 + 2 * BLEED_WIDTHS) * width + 2 * max) / (2 * min)
372
+ );
179
373
  if (bumps > MAX_BUMPS)
180
374
  throw new Error(
181
- `CloudBank: ${width} wide at min ${min} and max ${max} can take ${bumps} bumps; a bank draws at most ${MAX_BUMPS} bumps`
375
+ `CloudBank: a ${width}-wide field at min ${min} and max ${max} takes ${bumps} bumps with its bleed; a bank draws at most ${MAX_BUMPS}`
182
376
  );
377
+ const bottom = reach.down ? height * (1 + DOWN_SHARE) : height;
378
+ const under = max / 2;
379
+ const arc = (r, lift) => `a${r.toFixed(1)} ${r.toFixed(1)} 0 0 1 ${(r * 2).toFixed(1)} ${lift.toFixed(1)}`;
380
+ const rounded = (n) => Number(n.toFixed(1));
381
+ const step = (r, draw) => (draw() - 0.5) * r * 0.5 - climb * r * 2 / width;
183
382
  const next = rng(seed);
383
+ const run = [];
184
384
  let x = -max;
385
+ let endX = x;
386
+ let endY = y;
185
387
  let drift = 0;
186
- let d = `M${x} ${height}L${x} ${y}`;
187
388
  while (x < width + max) {
188
389
  const r = min + next() * (max - min);
189
- const step = (next() - 0.5) * r * 0.5 - climb * r * 2 / width;
190
- const lift = level ? step - drift : step;
390
+ const s = step(r, next);
391
+ const lift = level ? s - drift : s;
191
392
  drift += lift;
192
- d += `a${r.toFixed(1)} ${r.toFixed(1)} 0 0 1 ${(r * 2).toFixed(1)} ${lift.toFixed(1)}`;
193
393
  x += r * 2;
394
+ endX += rounded(r * 2);
395
+ endY += rounded(lift);
396
+ run.push({ d: arc(r, lift), x, endX, endY });
194
397
  }
195
- return /* @__PURE__ */ jsx("path", { d: `${d}L${x} ${height}Z`, className: FILL[tone] });
398
+ const end = (from, edge, up) => {
399
+ const rx = Math.abs(edge - from.x).toFixed(1);
400
+ const ry = bottom - from.y;
401
+ if (ry <= 0)
402
+ throw new Error(
403
+ `CloudBank: its end at the frame's edge has no depth to fall (top ${from.y.toFixed(1)}, fill to ${bottom}); a closed side fills on below the field`
404
+ );
405
+ return up ? `A${rx} ${ry.toFixed(1)} 0 0 1 ${from.x.toFixed(1)} ${from.y.toFixed(1)}` : `A${rx} ${ry.toFixed(1)} 0 0 1 ${edge} ${bottom}`;
406
+ };
407
+ const first = reach.left === "close" ? run.findIndex((b) => b.endX >= max) : -1;
408
+ let last = run.length - 1;
409
+ if (reach.right === "close") {
410
+ last = -1;
411
+ run.forEach((b, i) => {
412
+ if (b.endX <= width - max && i > first) last = i;
413
+ });
414
+ }
415
+ const startPoint = first === -1 ? { x: -max, y } : { x: run[first].endX, y: run[first].endY };
416
+ let frame = reach.left === "close" ? `M0 ${bottom}${end(startPoint, 0, true)}` : `M${-max} ${bottom}L${-max} ${y}`;
417
+ for (let i = first + 1; i <= last; i++) frame += run[i].d;
418
+ if (reach.right === "close") {
419
+ const at = last === -1 ? startPoint : { x: run[last].endX, y: run[last].endY };
420
+ frame += `${end(at, width, false)}Z`;
421
+ } else frame += `L${x} ${bottom}Z`;
422
+ const paths = [];
423
+ if (reach.left === "bleed") {
424
+ const back = rng(seed + BLEED_STREAM);
425
+ const arcs = [];
426
+ let lx = -max;
427
+ let offset = 0;
428
+ while (lx > -BLEED_WIDTHS * width - max) {
429
+ const r = min + back() * (max - min);
430
+ const s = step(r, back);
431
+ const lift = level ? offset - s : s;
432
+ offset -= rounded(lift);
433
+ arcs.unshift(arc(r, lift));
434
+ lx -= rounded(r * 2);
435
+ }
436
+ paths.push(
437
+ `M${lx.toFixed(1)} ${bottom}L${lx.toFixed(1)} ${(y + offset).toFixed(1)}${arcs.join("")}L${-max} ${y}L${-max + under} ${y + under}L${-max + under} ${bottom}Z`
438
+ );
439
+ }
440
+ if (reach.right === "bleed") {
441
+ let right = `M${(endX - under).toFixed(1)} ${bottom}L${(endX - under).toFixed(1)} ${(endY + under).toFixed(1)}L${endX.toFixed(1)} ${endY.toFixed(1)}`;
442
+ let rx = x;
443
+ while (rx < (1 + BLEED_WIDTHS) * width + max) {
444
+ const r = min + next() * (max - min);
445
+ const s = step(r, next);
446
+ const lift = level ? s - drift : s;
447
+ drift += lift;
448
+ right += arc(r, lift);
449
+ rx += r * 2;
450
+ }
451
+ paths.push(`${right}L${rx} ${bottom}Z`);
452
+ }
453
+ paths.push(frame);
454
+ return paths;
196
455
  }
197
- function Ridge({
198
- width,
199
- height,
200
- y,
201
- rise,
202
- swells,
203
- seed,
204
- tone
205
- }) {
206
- aboveZero("Ridge", { width, height });
207
- finite("Ridge", { y, rise, seed });
208
- wholeWithin("Ridge", "swells", swells, [1, MAX_SWELLS]);
209
- const next = rng(seed);
456
+ function CloudBank({ tone, ...bank }) {
457
+ const field = useField("CloudBank");
458
+ const paths = cloudBankPaths(field, bank, field.reach);
459
+ return /* @__PURE__ */ jsx2("g", { "data-scene-piece": "cloud-bank", className: FILL[tone], children: paths.map((d, i) => /* @__PURE__ */ jsx2("path", { d }, i)) });
460
+ }
461
+ function ridgePath(field, ridge, reach) {
462
+ const { y, rise, swells, seed } = ridge;
463
+ const { width, height } = field;
464
+ sized("Ridge", field);
465
+ measuredAll("Ridge", field, { y, rise, swells, seed });
466
+ const bottom = reach.down ? height * (1 + DOWN_SHARE) : height;
210
467
  const step = width / swells;
211
- let d = `M0 ${height}L0 ${y}`;
212
- for (let i = 0; i < swells; i++) {
468
+ let d;
469
+ if (reach.left === "close") d = `M0 ${bottom}`;
470
+ else {
471
+ const back = rng(seed + BLEED_STREAM);
472
+ const from = -BLEED_WIDTHS * width;
473
+ d = `M${from} ${bottom}L${from} ${(y - back() * rise * 0.3).toFixed(1)}`;
474
+ for (let i = -BLEED_WIDTHS * swells; i < 0; i++) {
475
+ const cy = y - back() * rise;
476
+ const end = i === -1 ? y : y - back() * rise * 0.3;
477
+ d += `Q${(i + 0.5) * step} ${cy.toFixed(1)} ${(i + 1) * step} ${end.toFixed(1)}`;
478
+ }
479
+ }
480
+ const next = rng(seed);
481
+ const count = reach.right === "close" ? swells : (1 + BLEED_WIDTHS) * swells;
482
+ for (let i = 0; i < count; i++) {
213
483
  const cy = y - next() * rise;
214
- d += `Q${(i + 0.5) * step} ${cy.toFixed(1)} ${(i + 1) * step} ${(y - next() * rise * 0.3).toFixed(1)}`;
484
+ const tail = (y - next() * rise * 0.3).toFixed(1);
485
+ const end = reach.right === "close" && i === count - 1 ? `${bottom}` : tail;
486
+ d += `Q${(i + 0.5) * step} ${cy.toFixed(1)} ${(i + 1) * step} ${end}`;
215
487
  }
216
- return /* @__PURE__ */ jsx("path", { d: `${d}L${width} ${height}Z`, className: FILL[tone] });
488
+ if (reach.right === "close") return `${d}Z`;
489
+ return `${d}L${(1 + BLEED_WIDTHS) * width} ${bottom}Z`;
490
+ }
491
+ function Ridge({ tone, ...ridge }) {
492
+ const field = useField("Ridge");
493
+ const d = ridgePath(field, ridge, field.reach);
494
+ return /* @__PURE__ */ jsx2("path", { "data-scene-piece": "ridge", d, className: FILL[tone] });
217
495
  }
218
- function Stars({ width, height, count, seed, tone }) {
219
- aboveZero("Stars", { width, height });
220
- finite("Stars", { seed });
221
- wholeWithin("Stars", "count", count, [0, MAX_STARS]);
496
+ function starDots(field, { depth, count, seed }) {
497
+ sized("Stars", field);
498
+ measuredAll("Stars", field, { depth, count, seed });
222
499
  const next = rng(seed);
223
- return /* @__PURE__ */ jsx("g", { className: FILL[tone], children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx(
224
- "circle",
225
- {
226
- cx: (next() * width).toFixed(1),
227
- cy: (next() * height).toFixed(1),
228
- r: (0.8 + next() * 1.4).toFixed(2)
229
- },
230
- i
231
- )) });
500
+ const reach = depth * field.height;
501
+ return Array.from({ length: count }, () => ({
502
+ cx: (next() * field.width).toFixed(1),
503
+ cy: (next() * reach).toFixed(1),
504
+ r: (0.8 + next() * 1.4).toFixed(2)
505
+ }));
232
506
  }
507
+ function Stars({ tone, ...stars }) {
508
+ const field = useField("Stars");
509
+ const dots = starDots(field, stars);
510
+ return /* @__PURE__ */ jsx2("g", { className: FILL[tone], children: dots.map((dot, i) => /* @__PURE__ */ jsx2("circle", { cx: dot.cx, cy: dot.cy, r: dot.r }, i)) });
511
+ }
512
+ var LABEL_HALO = 0.4;
233
513
  function RouteLine({
234
514
  points,
235
515
  tone,
236
516
  markTone,
237
517
  labelSize
238
518
  }) {
239
- aboveZero("RouteLine", { labelSize });
240
- points.forEach(
241
- (p, i) => finite("RouteLine", { [`points[${i}].x`]: p.x, [`points[${i}].y`]: p.y })
242
- );
519
+ const field = useField("RouteLine");
520
+ const layer = useContext(LayerContext);
521
+ measuredAll("RouteLine", field, { labelSize });
522
+ points.forEach((p, i) => {
523
+ measured("RouteLine", "points.x", `points[${i}].x`, p.x, field);
524
+ measured("RouteLine", "points.y", `points[${i}].y`, p.y, field);
525
+ });
526
+ if (!field.routes.has(points))
527
+ throw new Error(
528
+ "RouteLine: its labels paint in its SkyField's top layer, so it is a direct child of the SkyField; this one is inside another element"
529
+ );
530
+ const labelled = points.some((p) => p.label);
531
+ if (labelled && field.ground === null)
532
+ throw new Error(
533
+ `RouteLine: a label carries a halo in its field's ground tone; this SkyField has ground="none" and no Band around it gives one`
534
+ );
535
+ if (layer === "labels") {
536
+ if (!labelled || field.ground === null) return null;
537
+ const halo = STROKE[field.ground];
538
+ const gap = labelSize * 1.1;
539
+ return /* @__PURE__ */ jsx2(Fragment2, { children: points.map((p, i) => {
540
+ if (!p.label) return null;
541
+ const side = p.side ?? "above";
542
+ const lx = side === "left" ? p.x - gap : side === "right" ? p.x + gap : p.x;
543
+ const ly = side === "above" ? p.y - gap : side === "below" ? p.y + gap * 1.5 : p.y + labelSize * 0.35;
544
+ const anchor = side === "left" ? "end" : side === "right" ? "start" : "middle";
545
+ return /* @__PURE__ */ jsx2(
546
+ "text",
547
+ {
548
+ x: lx,
549
+ y: ly,
550
+ textAnchor: anchor,
551
+ fontSize: labelSize,
552
+ fontWeight: 600,
553
+ strokeWidth: labelSize * LABEL_HALO,
554
+ strokeLinejoin: "round",
555
+ paintOrder: "stroke",
556
+ className: cn(FILL[tone], halo),
557
+ children: p.label
558
+ },
559
+ i
560
+ );
561
+ }) });
562
+ }
243
563
  const at = (i) => points[Math.max(0, Math.min(points.length - 1, i))];
244
564
  let d = `M${points[0].x} ${points[0].y}`;
245
565
  for (let i = 0; i < points.length - 1; i++) {
246
566
  const [p0, p1, p2, p3] = [at(i - 1), at(i), at(i + 1), at(i + 2)];
247
567
  d += `C${(p1.x + (p2.x - p0.x) / 6).toFixed(1)} ${(p1.y + (p2.y - p0.y) / 6).toFixed(1)} ${(p2.x - (p3.x - p1.x) / 6).toFixed(1)} ${(p2.y - (p3.y - p1.y) / 6).toFixed(1)} ${p2.x} ${p2.y}`;
248
568
  }
249
- const gap = labelSize * 1.1;
250
569
  return /* @__PURE__ */ jsxs("g", { children: [
251
- /* @__PURE__ */ jsx(
570
+ /* @__PURE__ */ jsx2(
252
571
  "path",
253
572
  {
254
573
  d,
@@ -259,60 +578,54 @@ function RouteLine({
259
578
  className: STROKE[tone]
260
579
  }
261
580
  ),
262
- points.map((p, i) => {
263
- const side = p.side ?? "above";
264
- const lx = side === "left" ? p.x - gap : side === "right" ? p.x + gap : p.x;
265
- const ly = side === "above" ? p.y - gap : side === "below" ? p.y + gap * 1.5 : p.y + labelSize * 0.35;
266
- const anchor = side === "left" ? "end" : side === "right" ? "start" : "middle";
267
- return /* @__PURE__ */ jsxs("g", { children: [
268
- p.state === "here" && /* @__PURE__ */ jsx(
269
- "circle",
270
- {
271
- cx: p.x,
272
- cy: p.y,
273
- r: labelSize * 0.85,
274
- className: cn(FILL[markTone], "opacity-30")
275
- }
276
- ),
277
- /* @__PURE__ */ jsx(
278
- "circle",
279
- {
280
- cx: p.x,
281
- cy: p.y,
282
- r: labelSize * 0.34,
283
- strokeWidth: labelSize * 0.14,
284
- className: cn(
285
- STROKE[tone],
286
- p.state === "ahead" ? FILL[markTone] : FILL[tone]
287
- )
288
- }
289
- ),
290
- p.label && /* @__PURE__ */ jsx(
291
- "text",
292
- {
293
- x: lx,
294
- y: ly,
295
- textAnchor: anchor,
296
- fontSize: labelSize,
297
- fontWeight: 600,
298
- className: FILL[tone],
299
- children: p.label
300
- }
301
- )
302
- ] }, i);
303
- })
581
+ points.map((p, i) => /* @__PURE__ */ jsxs("g", { children: [
582
+ p.state === "here" && /* @__PURE__ */ jsx2(
583
+ "circle",
584
+ {
585
+ cx: p.x,
586
+ cy: p.y,
587
+ r: labelSize * 0.85,
588
+ className: cn(FILL[markTone], "opacity-30")
589
+ }
590
+ ),
591
+ /* @__PURE__ */ jsx2(
592
+ "circle",
593
+ {
594
+ cx: p.x,
595
+ cy: p.y,
596
+ r: labelSize * 0.34,
597
+ strokeWidth: labelSize * 0.14,
598
+ className: cn(
599
+ STROKE[tone],
600
+ p.state === "ahead" ? FILL[markTone] : FILL[tone]
601
+ )
602
+ }
603
+ )
604
+ ] }, i))
304
605
  ] });
305
606
  }
306
607
 
307
608
  // src/material/band.tsx
308
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
609
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
309
610
  var PAD = {
310
- none: "",
311
- tight: "pb-16 md:pb-24",
611
+ none: "[&:has(+[data-band-edge])]:pb-16 md:[&:has(+[data-band-edge])]:pb-[120px]",
612
+ tight: "pb-16 md:pb-24 md:[&:has(+[data-band-edge])]:pb-[120px]",
312
613
  section: "py-20 md:py-28",
313
614
  open: "py-24 md:py-40"
314
615
  };
616
+ var UNDER_NIGHT_HEADER = [
617
+ "[[data-marketing-header='night']+&]:before:block [[data-marketing-header='night']+&]:before:h-16 [[data-marketing-header='night']+&]:before:content-['']",
618
+ "[[data-marketing-header='night']+*>&:first-child]:before:block [[data-marketing-header='night']+*>&:first-child]:before:h-16 [[data-marketing-header='night']+*>&:first-child]:before:content-['']",
619
+ "[[data-marketing-header='night']+*>:first-child>&:first-child]:before:block [[data-marketing-header='night']+*>:first-child>&:first-child]:before:h-16 [[data-marketing-header='night']+*>:first-child>&:first-child]:before:content-['']"
620
+ ].join(" ");
315
621
  var EDGE_FIELD = { width: 3840, height: 240, seam: 120 };
622
+ function Bank({
623
+ field,
624
+ bank,
625
+ tone
626
+ }) {
627
+ return /* @__PURE__ */ jsx3("g", { className: FILL[tone], children: cloudBankPaths(field, bank, BLEED_BOTH_SIDES).map((d, i) => /* @__PURE__ */ jsx3("path", { d }, i)) });
628
+ }
316
629
  function BandEdge({ via, tone, seed }) {
317
630
  const { width, height, seam } = EDGE_FIELD;
318
631
  const overlap = 8;
@@ -320,46 +633,32 @@ function BandEdge({ via, tone, seed }) {
320
633
  "svg",
321
634
  {
322
635
  "aria-hidden": "true",
636
+ "data-band-edge-art": "",
323
637
  viewBox: `0 0 ${width} ${height}`,
324
638
  preserveAspectRatio: "xMidYMid slice",
325
639
  className: "pointer-events-none absolute inset-x-0 -top-16 h-32 w-full md:-top-[120px] md:h-[240px]",
326
640
  children: [
327
- via && /* @__PURE__ */ jsx2(
328
- CloudBank,
641
+ via && /* @__PURE__ */ jsx3(
642
+ Bank,
329
643
  {
330
- width,
331
- height: seam,
332
- y: 82,
333
- min: 24,
334
- max: 56,
335
- level: true,
336
- seed: seed + 3,
644
+ field: { width, height: seam },
645
+ bank: { y: 82, min: 24, max: 56, level: true, seed: seed + 3 },
337
646
  tone: via
338
647
  }
339
648
  ),
340
- /* @__PURE__ */ jsx2(
341
- CloudBank,
649
+ /* @__PURE__ */ jsx3(
650
+ Bank,
342
651
  {
343
- width,
344
- height: seam + overlap,
345
- y: 100,
346
- min: 22,
347
- max: 58,
348
- level: true,
349
- seed,
652
+ field: { width, height: seam + overlap },
653
+ bank: { y: 100, min: 22, max: 58, level: true, seed },
350
654
  tone
351
655
  }
352
656
  ),
353
- /* @__PURE__ */ jsx2("g", { transform: `matrix(1 0 0 -1 0 ${height})`, children: /* @__PURE__ */ jsx2(
354
- CloudBank,
657
+ /* @__PURE__ */ jsx3("g", { transform: `matrix(1 0 0 -1 0 ${height})`, children: /* @__PURE__ */ jsx3(
658
+ Bank,
355
659
  {
356
- width,
357
- height: seam + overlap,
358
- y: 72,
359
- min: 22,
360
- max: 58,
361
- level: true,
362
- seed: seed + 7,
660
+ field: { width, height: seam + overlap },
661
+ bank: { y: 72, min: 22, max: 58, level: true, seed: seed + 7 },
363
662
  tone
364
663
  }
365
664
  ) })
@@ -383,16 +682,18 @@ function Band({
383
682
  {
384
683
  id,
385
684
  "data-surface": surface,
685
+ "data-band-edge": edge ? "" : void 0,
386
686
  className: cn(
387
687
  "relative isolate scroll-mt-4",
388
688
  BG[tone],
389
689
  PAD[pad],
390
690
  under === "overhang" && "pt-44 md:pt-[23rem]",
391
691
  edge && "pt-28 md:pt-48",
392
- raised && "z-10"
692
+ raised && "z-10",
693
+ surface === "night" && UNDER_NIGHT_HEADER
393
694
  ),
394
695
  children: [
395
- backdrop && /* @__PURE__ */ jsx2(
696
+ backdrop && /* @__PURE__ */ jsx3(
396
697
  "div",
397
698
  {
398
699
  "aria-hidden": "true",
@@ -400,15 +701,15 @@ function Band({
400
701
  children: backdrop
401
702
  }
402
703
  ),
403
- edge && /* @__PURE__ */ jsx2(BandEdge, { ...edge, tone }),
404
- children
704
+ edge && /* @__PURE__ */ jsx3(BandEdge, { ...edge, tone }),
705
+ /* @__PURE__ */ jsx3(GroundContext.Provider, { value: tone, children })
405
706
  ]
406
707
  }
407
708
  );
408
709
  }
409
710
 
410
711
  // src/material/figure.tsx
411
- import { jsx as jsx3 } from "react/jsx-runtime";
712
+ import { jsx as jsx4 } from "react/jsx-runtime";
412
713
  var ASPECT = {
413
714
  "16:9": "aspect-video",
414
715
  "8:5": "aspect-[8/5]",
@@ -431,7 +732,7 @@ function recordable(spec) {
431
732
  }
432
733
  function Figure({ spec, alt, children, fit = "aspect" }) {
433
734
  recordable(spec);
434
- return /* @__PURE__ */ jsx3(
735
+ return /* @__PURE__ */ jsx4(
435
736
  "figure",
436
737
  {
437
738
  role: alt ? "img" : void 0,
@@ -450,8 +751,55 @@ function Figure({ spec, alt, children, fit = "aspect" }) {
450
751
  );
451
752
  }
452
753
 
754
+ // src/material/hero-scene.tsx
755
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
756
+ var STACK_ZONE_RATIO = 1.25;
757
+ var STACK_ZONE_MIN_WIDTH = 390;
758
+ var HEIGHT2 = {
759
+ fold: "lg:min-h-[min(calc(100svh-4rem),56rem)]",
760
+ tall: "lg:min-h-[46rem]"
761
+ };
762
+ function HeroScene({
763
+ height,
764
+ subject,
765
+ copySide = "start",
766
+ children
767
+ }) {
768
+ return /* @__PURE__ */ jsx5("div", { "data-hero-scene": "", className: "overflow-x-clip", children: /* @__PURE__ */ jsxs3(
769
+ "div",
770
+ {
771
+ className: cn(
772
+ "mx-auto flex w-full max-w-content-xl flex-col lg:grid lg:grid-cols-2",
773
+ HEIGHT2[height]
774
+ ),
775
+ children: [
776
+ /* @__PURE__ */ jsx5(
777
+ "div",
778
+ {
779
+ "data-hero-copy": "",
780
+ className: cn(
781
+ "relative z-10 flex min-w-0 flex-col items-start justify-center gap-6 py-10 md:py-14",
782
+ CONTENT_GUTTER,
783
+ copySide === "end" && "lg:order-2"
784
+ ),
785
+ children
786
+ }
787
+ ),
788
+ /* @__PURE__ */ jsx5(
789
+ "div",
790
+ {
791
+ "data-hero-subject": "",
792
+ className: "relative mx-auto aspect-[5/4] w-full max-w-2xl lg:aspect-auto lg:h-full lg:max-w-none [&_figure]:overflow-visible [&_svg]:overflow-visible",
793
+ children: /* @__PURE__ */ jsx5(SceneCopyContext.Provider, { value: copySide, children: subject })
794
+ }
795
+ )
796
+ ]
797
+ }
798
+ ) });
799
+ }
800
+
453
801
  // src/material/layout.tsx
454
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
802
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
455
803
  var GAP = {
456
804
  1: "gap-1",
457
805
  2: "gap-2",
@@ -464,7 +812,7 @@ var GAP = {
464
812
  16: "gap-16"
465
813
  };
466
814
  function Stack({ gap, align = "stretch", children }) {
467
- return /* @__PURE__ */ jsx4(
815
+ return /* @__PURE__ */ jsx6(
468
816
  "div",
469
817
  {
470
818
  className: cn(
@@ -484,7 +832,7 @@ function Inline({
484
832
  fillOnPhone,
485
833
  children
486
834
  }) {
487
- return /* @__PURE__ */ jsx4(
835
+ return /* @__PURE__ */ jsx6(
488
836
  "div",
489
837
  {
490
838
  className: cn(
@@ -512,7 +860,7 @@ function Columns({
512
860
  connect,
513
861
  children
514
862
  }) {
515
- return /* @__PURE__ */ jsxs3(
863
+ return /* @__PURE__ */ jsxs4(
516
864
  "div",
517
865
  {
518
866
  className: cn(
@@ -526,13 +874,13 @@ function Columns({
526
874
  ),
527
875
  children: [
528
876
  children,
529
- phone === "scroll" && /* @__PURE__ */ jsx4(ScrollHintXMark, {})
877
+ phone === "scroll" && /* @__PURE__ */ jsx6(ScrollHintXMark, {})
530
878
  ]
531
879
  }
532
880
  );
533
881
  }
534
882
  function Split({ ratio, reverse, gap, children }) {
535
- return /* @__PURE__ */ jsxs3(
883
+ return /* @__PURE__ */ jsxs4(
536
884
  "div",
537
885
  {
538
886
  className: cn(
@@ -542,27 +890,26 @@ function Split({ ratio, reverse, gap, children }) {
542
890
  reverse && (ratio === "even" ? "md:[&>:first-child]:order-2" : "md:grid-cols-[7fr_5fr] md:[&>:first-child]:order-2")
543
891
  ),
544
892
  children: [
545
- /* @__PURE__ */ jsx4("div", { className: "min-w-0", children: children[0] }),
546
- /* @__PURE__ */ jsx4("div", { className: "min-w-0", children: children[1] })
893
+ /* @__PURE__ */ jsx6("div", { className: "min-w-0", children: children[0] }),
894
+ /* @__PURE__ */ jsx6("div", { className: "min-w-0", children: children[1] })
547
895
  ]
548
896
  }
549
897
  );
550
898
  }
551
899
  var SPACER = {
552
900
  hero: "h-10 md:h-14",
553
- section: "h-16 md:h-24",
554
- "phone-sky": "h-40 md:hidden"
901
+ section: "h-16 md:h-24"
555
902
  };
556
903
  function Spacer({ size }) {
557
- return /* @__PURE__ */ jsx4("div", { "aria-hidden": "true", className: SPACER[size] });
904
+ return /* @__PURE__ */ jsx6("div", { "aria-hidden": "true", className: SPACER[size] });
558
905
  }
559
906
  var INSET = { 3: "p-3", 4: "p-4", 6: "p-6" };
560
907
  function Inset({ pad, children }) {
561
- return /* @__PURE__ */ jsx4("div", { className: INSET[pad], children });
908
+ return /* @__PURE__ */ jsx6("div", { className: INSET[pad], children });
562
909
  }
563
910
 
564
911
  // src/material/type.tsx
565
- import { jsx as jsx5 } from "react/jsx-runtime";
912
+ import { jsx as jsx7 } from "react/jsx-runtime";
566
913
  var LEVEL = {
567
914
  poster: "text-display-poster",
568
915
  headline: "text-display-headline",
@@ -580,7 +927,7 @@ function Display({
580
927
  measure = "none",
581
928
  children
582
929
  }) {
583
- return /* @__PURE__ */ jsx5(
930
+ return /* @__PURE__ */ jsx7(
584
931
  Tag,
585
932
  {
586
933
  className: cn(
@@ -618,7 +965,7 @@ function Text({
618
965
  as: Tag = "p",
619
966
  children
620
967
  }) {
621
- return /* @__PURE__ */ jsx5(
968
+ return /* @__PURE__ */ jsx7(
622
969
  Tag,
623
970
  {
624
971
  className: cn(
@@ -634,7 +981,7 @@ function Text({
634
981
  }
635
982
 
636
983
  // src/material/vignette.tsx
637
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
984
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
638
985
  function Vignette({
639
986
  title,
640
987
  icon,
@@ -644,7 +991,7 @@ function Vignette({
644
991
  crop = "none",
645
992
  children
646
993
  }) {
647
- return /* @__PURE__ */ jsxs4(
994
+ return /* @__PURE__ */ jsxs5(
648
995
  "div",
649
996
  {
650
997
  "data-surface": surface,
@@ -653,12 +1000,12 @@ function Vignette({
653
1000
  placement === "hang" && "z-10 -mb-32 md:-mb-52"
654
1001
  ),
655
1002
  children: [
656
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 border-b border-border bg-card px-4 py-2.5", children: [
657
- /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx6(Icon, { name: icon, size: "sm" }) }),
658
- /* @__PURE__ */ jsx6("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: title }),
659
- /* @__PURE__ */ jsx6(IntentBadge, { intent: "neutral", children: sampleLabel })
1003
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 border-b border-border bg-card px-4 py-2.5", children: [
1004
+ /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx8(Icon, { name: icon, size: "sm" }) }),
1005
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground", children: title }),
1006
+ /* @__PURE__ */ jsx8(IntentBadge, { intent: "neutral", children: sampleLabel })
660
1007
  ] }),
661
- /* @__PURE__ */ jsx6(
1008
+ /* @__PURE__ */ jsx8(
662
1009
  "div",
663
1010
  {
664
1011
  className: cn(
@@ -675,31 +1022,36 @@ function Vignette({
675
1022
  }
676
1023
 
677
1024
  // src/material/wordmark.tsx
678
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1025
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
679
1026
  function Wordmark({ name, mark, href }) {
680
- return /* @__PURE__ */ jsxs5(
1027
+ return /* @__PURE__ */ jsxs6(
681
1028
  "a",
682
1029
  {
683
1030
  href,
684
1031
  className: "flex items-center gap-2 text-lg font-bold tracking-tight text-foreground [font-family:var(--font-heading,inherit)]",
685
1032
  children: [
686
- /* @__PURE__ */ jsx7("span", { className: "text-primary-text size-6 shrink-0", children: mark }),
1033
+ /* @__PURE__ */ jsx9("span", { className: "text-primary-text size-6 shrink-0", children: mark }),
687
1034
  name
688
1035
  ]
689
1036
  }
690
1037
  );
691
1038
  }
692
1039
  export {
1040
+ BLEED_BOTH_SIDES,
693
1041
  Band,
694
1042
  CloudBank,
695
1043
  Columns,
696
1044
  Display,
697
1045
  Figure,
698
1046
  Halo,
1047
+ HeroScene,
699
1048
  Inline,
700
1049
  Inset,
701
1050
  Ridge,
702
1051
  RouteLine,
1052
+ SCENE_MEASURES,
1053
+ STACK_ZONE_MIN_WIDTH,
1054
+ STACK_ZONE_RATIO,
703
1055
  SkyField,
704
1056
  Spacer,
705
1057
  Split,
@@ -707,5 +1059,8 @@ export {
707
1059
  Stars,
708
1060
  Text,
709
1061
  Vignette,
710
- Wordmark
1062
+ Wordmark,
1063
+ cloudBankPaths,
1064
+ ridgePath,
1065
+ starDots
711
1066
  };