@caqophony/ui 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -346,7 +346,7 @@ function NavbarMenu({
346
346
  defaultOpen = false,
347
347
  triggerClassName,
348
348
  folded = false,
349
- surface
349
+ night = false
350
350
  }) {
351
351
  return /* @__PURE__ */ jsx5("div", { className: folded ? "ml-auto" : "ml-auto md:hidden", children: /* @__PURE__ */ jsxs4(
352
352
  NavDrawer,
@@ -356,7 +356,7 @@ function NavbarMenu({
356
356
  closedFrom: folded ? null : "md",
357
357
  defaultOpen,
358
358
  triggerClassName,
359
- surface,
359
+ surface: night ? "night" : void 0,
360
360
  children: [
361
361
  search && /* @__PURE__ */ jsx5("div", { className: "px-4 pb-3", children: search }),
362
362
  links && /* @__PURE__ */ jsx5("nav", { className: "flex flex-col px-4 pb-3 [&>a]:whitespace-normal [&>a]:py-2.5 [&>a]:text-base", children: links }),
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  buttonVariants,
12
12
  useModalSurface,
13
13
  usePanelRef
14
- } from "./chunk-I5DQCG6D.js";
14
+ } from "./chunk-WGWKCFG2.js";
15
15
  import {
16
16
  Alert,
17
17
  EMPTY_VALUE_GLYPH,
@@ -41,8 +41,10 @@ interface MarketingHeaderProps {
41
41
  * through the bar; its drawer stands on the same scope.
42
42
  *
43
43
  * #5948: below `md` the links and actions fold into the library's one phone
44
- * drawer (`NavbarMenu`), in both variants. The drawer stands on the card
45
- * surface whichever the variant, so its links are drawn for that surface.
44
+ * drawer (`NavbarMenu`), in both variants. A `light` bar's drawer stands on
45
+ * the card surface. #5997: a `dark` bar's stands on the night scope, as a
46
+ * `night` bar's does (#5980): its actions are `MarketingCta ground="dark"`,
47
+ * which read near-white on a day card.
46
48
  *
47
49
  * #5952: the row has a limit, and past it the bar folds into the same drawer at
48
50
  * any width (`useFold`), so a brand is never squeezed to make room for links.
package/dist/marketing.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  Button,
4
4
  Container,
5
5
  NavbarMenu
6
- } from "./chunk-I5DQCG6D.js";
6
+ } from "./chunk-WGWKCFG2.js";
7
7
  import {
8
8
  ScrollHintXMark
9
9
  } from "./chunk-ID7VBGJA.js";
@@ -196,7 +196,7 @@ function MarketingHeader({
196
196
  {
197
197
  folded,
198
198
  triggerClassName: isDark ? DARK_MENU_BUTTON : isNight ? NIGHT_MENU_BUTTON : void 0,
199
- surface: isNight ? variant : void 0,
199
+ night: isNight || isDark,
200
200
  links: hasLinks ? navItems.map((item) => /* @__PURE__ */ jsx(
201
201
  "a",
202
202
  {
@@ -92,6 +92,20 @@ interface ArtPoly extends ArtFilled {
92
92
  el: 'polygon' | 'polyline';
93
93
  points: ReadonlyArray<readonly [number, number]>;
94
94
  }
95
+ /**
96
+ * #5998 — a ground: the author states where it sits in its frame (`y`) and its
97
+ * tone, and the code runs it the scene's full width and down past its foot
98
+ * (`GROUND_BLEED`), as the approved ports drew theirs by hand (x -4000 to
99
+ * 8000). It stands in a frame's own `nodes` only (the format's check names one
100
+ * in a group or a motif), so no transform moves its bleed. A `HeroScene` clips
101
+ * the sides, and the next band paints over the foot.
102
+ */
103
+ interface ArtGround {
104
+ el: 'ground';
105
+ y: number;
106
+ fill?: ArtPaint;
107
+ opacity?: number;
108
+ }
95
109
  interface ArtText extends ArtFilled {
96
110
  el: 'text';
97
111
  x: number;
@@ -102,7 +116,7 @@ interface ArtText extends ArtFilled {
102
116
  /** Words on the page, apart from the drawing: step 5 holds them to the Brief. */
103
117
  copy: string;
104
118
  }
105
- type ArtNode = ArtGroup | ArtPath | ArtRect | ArtCircle | ArtEllipse | ArtLine | ArtPoly | ArtText;
119
+ type ArtNode = ArtGroup | ArtPath | ArtRect | ArtCircle | ArtEllipse | ArtLine | ArtPoly | ArtText | ArtGround;
106
120
  /** Where a frame stays when its box has another shape. */
107
121
  type ArtAnchor = 'top' | 'center' | 'bottom' | 'right';
108
122
  /** `cover` fills the box and may cut the frame; `contain` keeps all of it. */
@@ -127,11 +141,30 @@ interface ArtTree {
127
141
  }
128
142
  /** The one writer of how a frame sits in its box, for `Art` and `SkyField`. */
129
143
  declare const preserveOf: (anchor: ArtAnchor, fit?: ArtFit) => string;
144
+ /**
145
+ * #6002 — how an `Art` frame sits in its box. A frame whose own `nodes` hold a
146
+ * `ground` stands on its box's foot (`YMax`) whatever its `anchor`, whose
147
+ * horizontal part it keeps: a hero's side frame is `contain`ed in a column
148
+ * taller than it, and a centred one put its ground at the copy's height
149
+ * (blind check 9, jobpilot-a: `y` 840 of 1000 under the CTAs from 1024 up).
150
+ * The objects standing on the ground are in the same frame units, so the whole
151
+ * frame moves, never the ground alone. Every other frame, and `SkyField`, keep
152
+ * `preserveOf`.
153
+ */
154
+ declare const framePreserveOf: (frame: Pick<ArtFrame, "anchor" | "fit" | "nodes">) => string;
130
155
  /** The one writer of where frames are cut, for `Art` and `SkyField`. */
131
156
  declare const frameClass: (only: ArtOnly | undefined) => string;
157
+ /**
158
+ * #5998 — how far a ground runs past its frame, each way and down, in the
159
+ * frame's larger side. Measured on the gencheck-8 answers and the three ports
160
+ * at every gate width: the scene runs past a frame's drawn content by at most
161
+ * 1.09 of that side (1600px wide), and from `lg` up the side draws at 750px
162
+ * or more, so 8 covers a scene about 6,600px wide.
163
+ */
164
+ declare const GROUND_BLEED = 8;
132
165
  /** Draws a piece of art, for a `Figure`'s slot or a mark. */
133
166
  declare function Art({ tree }: {
134
167
  tree: ArtTree;
135
168
  }): ReactElement;
136
169
 
137
- export { Art, type ArtAnchor, type ArtCircle, type ArtDrawing, type ArtEllipse, type ArtFilled, type ArtFit, type ArtFrame, type ArtGroup, type ArtLine, type ArtNode, type ArtOnly, type ArtPaint, type ArtPath, type ArtPoly, type ArtRect, type ArtText, type ArtTransform, type ArtTree, frameClass, preserveOf };
170
+ export { Art, type ArtAnchor, type ArtCircle, type ArtDrawing, type ArtEllipse, type ArtFilled, type ArtFit, type ArtFrame, type ArtGround, type ArtGroup, type ArtLine, type ArtNode, type ArtOnly, type ArtPaint, type ArtPath, type ArtPoly, type ArtRect, type ArtText, type ArtTransform, type ArtTree, GROUND_BLEED, frameClass, framePreserveOf, preserveOf };
@@ -48,7 +48,15 @@ var ANCHOR = {
48
48
  bottom: "xMidYMax",
49
49
  right: "xMaxYMid"
50
50
  };
51
- var preserveOf = (anchor, fit = "cover") => `${ANCHOR[anchor]} ${fit === "contain" ? "meet" : "slice"}`;
51
+ var FIT = { contain: "meet", cover: "slice" };
52
+ var preserveOf = (anchor, fit = "cover") => `${ANCHOR[anchor]} ${FIT[fit]}`;
53
+ var ANCHOR_X = {
54
+ top: "xMid",
55
+ center: "xMid",
56
+ bottom: "xMid",
57
+ right: "xMax"
58
+ };
59
+ var framePreserveOf = (frame) => frame.nodes.some((node) => node.el === "ground") ? `${ANCHOR_X[frame.anchor]}YMax ${FIT[frame.fit ?? "cover"]}` : preserveOf(frame.anchor, frame.fit);
52
60
  var SHOWN = {
53
61
  phone: "size-full md:hidden",
54
62
  desktop: "hidden size-full md:block",
@@ -96,10 +104,14 @@ function drawn(node) {
96
104
  transform: node.transform?.map(transformOf).join(" ")
97
105
  };
98
106
  }
99
- function render(node, key) {
107
+ var GROUND_BLEED = 8;
108
+ function render(frame) {
109
+ return (node, key) => drawNode(node, key, frame);
110
+ }
111
+ function drawNode(node, key, frame) {
100
112
  switch (node.el) {
101
113
  case "g":
102
- return /* @__PURE__ */ jsx("g", { ...drawn(node), children: node.children.map(render) }, key);
114
+ return /* @__PURE__ */ jsx("g", { ...drawn(node), children: node.children.map(render(frame)) }, key);
103
115
  case "path":
104
116
  return /* @__PURE__ */ jsx("path", { d: node.d, ...drawn(node) }, key);
105
117
  case "rect":
@@ -169,6 +181,22 @@ function render(node, key) {
169
181
  },
170
182
  key
171
183
  );
184
+ case "ground": {
185
+ const bleed = GROUND_BLEED * Math.max(frame.width, frame.height);
186
+ return /* @__PURE__ */ jsx(
187
+ "rect",
188
+ {
189
+ "data-art-ground": "",
190
+ x: -bleed,
191
+ y: node.y,
192
+ width: frame.width + 2 * bleed,
193
+ height: Math.max(frame.height - node.y, 0) + bleed,
194
+ opacity: node.opacity,
195
+ ...painted(node)
196
+ },
197
+ key
198
+ );
199
+ }
172
200
  case "text":
173
201
  return /* @__PURE__ */ jsx(
174
202
  "text",
@@ -195,16 +223,19 @@ function Art({ tree }) {
195
223
  "svg",
196
224
  {
197
225
  viewBox: `0 0 ${frame.width} ${frame.height}`,
198
- preserveAspectRatio: preserveOf(frame.anchor, frame.fit),
226
+ preserveAspectRatio: framePreserveOf(frame),
227
+ "data-art-only": frame.only,
199
228
  "aria-hidden": "true",
200
229
  className: frameClass(frame.only),
201
- children: frame.nodes.map(render)
230
+ children: frame.nodes.map(render(frame))
202
231
  },
203
232
  i
204
233
  )) });
205
234
  }
206
235
  export {
207
236
  Art,
237
+ GROUND_BLEED,
208
238
  frameClass,
239
+ framePreserveOf,
209
240
  preserveOf
210
241
  };
@@ -198,14 +198,13 @@ interface FieldSize {
198
198
  height: number;
199
199
  }
200
200
  /**
201
- * #5985 round 2 — which way a field's horizon pieces run on past it. On a
202
- * `close` side a piece ends at the frame's edge with a natural end (a bank's
203
- * last bump, a ridge's slope, down to the field's bottom); `down` runs every
204
- * fill on below the field's bottom.
201
+ * #5985 round 2 — how far a field's horizon pieces run on past it: every piece
202
+ * bleeds both ways (`BLEED_WIDTHS`), and `down` runs every fill on below the
203
+ * field's bottom. #5997 retired the copy-side end (`close`): the approved port
204
+ * runs its ground the hero's full width, under the copy, and an end inside the
205
+ * scene showed as a tongue on every generated hero (generation check 7, 4 of 4).
205
206
  */
206
207
  interface FieldReach {
207
- left: 'bleed' | 'close';
208
- right: 'bleed' | 'close';
209
208
  down: boolean;
210
209
  }
211
210
  /** A field outside a `HeroScene`, `Band`'s edge and the stored art: the bleed both ways, filled to the field's bottom. */
@@ -253,10 +252,7 @@ interface CloudBankProps {
253
252
  * `contain`, and no start edge lands in a zone.
254
253
  *
255
254
  * #5985 round 2 — `reach` (a `HeroScene`'s field): `down` fills every path on
256
- * `DOWN_SHARE` of a field-height below the field's bottom, and a `close` side
257
- * has no bleed: the run ends with a last bump, a quarter ellipse from the run's
258
- * top down to the frame's edge at the foot of that fill, at least a `max` wide,
259
- * so it never stands as a straight edge.
255
+ * `DOWN_SHARE` of a field-height below the field's bottom.
260
256
  */
261
257
  declare function cloudBankPaths(field: FieldSize, bank: Omit<CloudBankProps, 'tone'>, reach: FieldReach): readonly string[];
262
258
  /**
@@ -283,9 +279,7 @@ interface RidgeProps {
283
279
  * pixels from before to keep.
284
280
  *
285
281
  * #5985 round 2 — `down` fills on `DOWN_SHARE` of a field-height below the
286
- * field's bottom; on a `close` side (a `HeroScene`'s field, toward its copy)
287
- * there is no bleed: the swell at the edge slopes down to the frame's edge at
288
- * the foot of that fill.
282
+ * field's bottom.
289
283
  */
290
284
  declare function ridgePath(field: FieldSize, ridge: Omit<RidgeProps, 'tone'>, reach: FieldReach): string;
291
285
  /** A smooth ridge line across its field, filled to the field's bottom. */
@@ -309,7 +303,11 @@ interface RoutePoint {
309
303
  x: number;
310
304
  y: number;
311
305
  label?: string;
312
- /** Which side of the point its label sits. */
306
+ /**
307
+ * Which side of the point its label sits, first: the route keeps it unless
308
+ * the label would cross another or a mark, or leave the field, and then
309
+ * places it on the first side clear of them (#5997).
310
+ */
313
311
  side?: 'above' | 'below' | 'left' | 'right';
314
312
  /** `passed`: filled; `here`: ringed; `ahead`: hollow. */
315
313
  state: 'passed' | 'here' | 'ahead';
package/dist/material.js CHANGED
@@ -89,7 +89,8 @@ var ANCHOR = {
89
89
  bottom: "xMidYMax",
90
90
  right: "xMaxYMid"
91
91
  };
92
- var preserveOf = (anchor, fit = "cover") => `${ANCHOR[anchor]} ${fit === "contain" ? "meet" : "slice"}`;
92
+ var FIT = { contain: "meet", cover: "slice" };
93
+ var preserveOf = (anchor, fit = "cover") => `${ANCHOR[anchor]} ${FIT[fit]}`;
93
94
  var SHOWN = {
94
95
  phone: "size-full md:hidden",
95
96
  desktop: "hidden size-full md:block",
@@ -263,11 +264,8 @@ function sized(piece, field) {
263
264
  );
264
265
  }
265
266
  var MAX_BUMPS = 2e3;
266
- var BLEED_BOTH_SIDES = {
267
- left: "bleed",
268
- right: "bleed",
269
- down: false
270
- };
267
+ var BLEED_BOTH_SIDES = { down: false };
268
+ var IN_SCENE = { down: true };
271
269
  var SceneCopyContext = createContext(null);
272
270
  var GroundContext = createContext(null);
273
271
  var FieldContext = createContext(null);
@@ -280,11 +278,8 @@ function useField(piece) {
280
278
  );
281
279
  return field;
282
280
  }
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 };
281
+ function reachOf(copy) {
282
+ return copy === null ? BLEED_BOTH_SIDES : IN_SCENE;
288
283
  }
289
284
  function SkyField({
290
285
  width,
@@ -304,7 +299,7 @@ function SkyField({
304
299
  const field = {
305
300
  width,
306
301
  height,
307
- reach: reachOf(copy, only),
302
+ reach: reachOf(copy),
308
303
  ground: ground === "none" ? under : ground,
309
304
  routes: new Set(
310
305
  routes.map(
@@ -380,7 +375,7 @@ function cloudBankPaths(field, bank, reach) {
380
375
  const rounded = (n) => Number(n.toFixed(1));
381
376
  const step = (r, draw) => (draw() - 0.5) * r * 0.5 - climb * r * 2 / width;
382
377
  const next = rng(seed);
383
- const run = [];
378
+ let frame = `M${-max} ${bottom}L${-max} ${y}`;
384
379
  let x = -max;
385
380
  let endX = x;
386
381
  let endY = y;
@@ -393,63 +388,35 @@ function cloudBankPaths(field, bank, reach) {
393
388
  x += r * 2;
394
389
  endX += rounded(r * 2);
395
390
  endY += rounded(lift);
396
- run.push({ d: arc(r, lift), x, endX, endY });
397
- }
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
- });
391
+ frame += arc(r, lift);
414
392
  }
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
- );
393
+ frame += `L${x} ${bottom}Z`;
394
+ const back = rng(seed + BLEED_STREAM);
395
+ const arcs = [];
396
+ let lx = -max;
397
+ let offset = 0;
398
+ while (lx > -BLEED_WIDTHS * width - max) {
399
+ const r = min + back() * (max - min);
400
+ const s = step(r, back);
401
+ const lift = level ? offset - s : s;
402
+ offset -= rounded(lift);
403
+ arcs.unshift(arc(r, lift));
404
+ lx -= rounded(r * 2);
439
405
  }
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`);
406
+ const paths = [
407
+ `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`
408
+ ];
409
+ let right = `M${(endX - under).toFixed(1)} ${bottom}L${(endX - under).toFixed(1)} ${(endY + under).toFixed(1)}L${endX.toFixed(1)} ${endY.toFixed(1)}`;
410
+ let rx = x;
411
+ while (rx < (1 + BLEED_WIDTHS) * width + max) {
412
+ const r = min + next() * (max - min);
413
+ const s = step(r, next);
414
+ const lift = level ? s - drift : s;
415
+ drift += lift;
416
+ right += arc(r, lift);
417
+ rx += r * 2;
452
418
  }
419
+ paths.push(`${right}L${rx} ${bottom}Z`);
453
420
  paths.push(frame);
454
421
  return paths;
455
422
  }
@@ -465,27 +432,20 @@ function ridgePath(field, ridge, reach) {
465
432
  measuredAll("Ridge", field, { y, rise, swells, seed });
466
433
  const bottom = reach.down ? height * (1 + DOWN_SHARE) : height;
467
434
  const step = width / swells;
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
- }
435
+ const back = rng(seed + BLEED_STREAM);
436
+ const from = -BLEED_WIDTHS * width;
437
+ let d = `M${from} ${bottom}L${from} ${(y - back() * rise * 0.3).toFixed(1)}`;
438
+ for (let i = -BLEED_WIDTHS * swells; i < 0; i++) {
439
+ const cy = y - back() * rise;
440
+ const end = i === -1 ? y : y - back() * rise * 0.3;
441
+ d += `Q${(i + 0.5) * step} ${cy.toFixed(1)} ${(i + 1) * step} ${end.toFixed(1)}`;
479
442
  }
480
443
  const next = rng(seed);
481
- const count = reach.right === "close" ? swells : (1 + BLEED_WIDTHS) * swells;
482
- for (let i = 0; i < count; i++) {
444
+ for (let i = 0; i < (1 + BLEED_WIDTHS) * swells; i++) {
483
445
  const cy = y - next() * rise;
484
446
  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}`;
447
+ d += `Q${(i + 0.5) * step} ${cy.toFixed(1)} ${(i + 1) * step} ${tail}`;
487
448
  }
488
- if (reach.right === "close") return `${d}Z`;
489
449
  return `${d}L${(1 + BLEED_WIDTHS) * width} ${bottom}Z`;
490
450
  }
491
451
  function Ridge({ tone, ...ridge }) {
@@ -509,7 +469,71 @@ function Stars({ tone, ...stars }) {
509
469
  const dots = starDots(field, stars);
510
470
  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
471
  }
512
- var LABEL_HALO = 0.4;
472
+ var LABEL_HALO = 0.12;
473
+ var LABEL_ADVANCE_EM = 0.62;
474
+ function routeLabelPlaces(points, labelSize, field) {
475
+ const gap = labelSize * 1.1;
476
+ const pad = labelSize * LABEL_HALO / 2;
477
+ const marks = points.map((p) => {
478
+ const r = labelSize * (p.state === "here" ? 0.85 : 0.34);
479
+ return {
480
+ what: `the mark at ${p.x},${p.y}`,
481
+ box: { left: p.x - r, top: p.y - r, right: p.x + r, bottom: p.y + r }
482
+ };
483
+ });
484
+ const placed = [];
485
+ return points.map((p, i) => {
486
+ if (!p.label) return null;
487
+ const words = p.label;
488
+ const width = words.length * LABEL_ADVANCE_EM * labelSize;
489
+ const before = points[Math.max(0, i - 1)];
490
+ const after = points[Math.min(points.length - 1, i + 1)];
491
+ const across = Math.abs(after.x - before.x) >= Math.abs(after.y - before.y);
492
+ const order = across ? ["above", "below", "right", "left"] : ["right", "left", "above", "below"];
493
+ const sides = p.side === void 0 ? order : [p.side, ...order.filter((side) => side !== p.side)];
494
+ const candidates = sides.map((side) => {
495
+ const x = side === "left" ? p.x - gap : side === "right" ? p.x + gap : p.x;
496
+ const y = side === "above" ? p.y - gap : side === "below" ? p.y + gap * 1.5 : p.y + labelSize * 0.35;
497
+ const anchor = side === "left" ? "end" : side === "right" ? "start" : "middle";
498
+ const left = anchor === "start" ? x : anchor === "end" ? x - width : x - width / 2;
499
+ return {
500
+ x,
501
+ y,
502
+ anchor,
503
+ side,
504
+ box: {
505
+ left: left - pad,
506
+ right: left + width + pad,
507
+ top: y - labelSize * 0.8 - pad,
508
+ bottom: y + labelSize * 0.25 + pad
509
+ }
510
+ };
511
+ });
512
+ const others = [...placed, ...marks.filter((_, k) => k !== i)];
513
+ const blocks = (c) => [
514
+ ...others.map((o) => ({
515
+ what: o.what,
516
+ across: Math.min(c.box.right, o.box.right) - Math.max(c.box.left, o.box.left),
517
+ down: Math.min(c.box.bottom, o.box.bottom) - Math.max(c.box.top, o.box.top)
518
+ })).filter((h) => h.across > 0 && h.down > 0).map(
519
+ (h) => `crosses ${h.what} by ${h.across.toFixed(1)} by ${h.down.toFixed(1)}`
520
+ ),
521
+ ...[
522
+ { edge: "left", by: -c.box.left },
523
+ { edge: "top", by: -c.box.top },
524
+ { edge: "right", by: c.box.right - field.width },
525
+ { edge: "bottom", by: c.box.bottom - field.height }
526
+ ].filter((e) => e.by > 0).map((e) => `runs ${e.by.toFixed(1)} past the field's ${e.edge}`)
527
+ ];
528
+ const clear = candidates.find((c) => blocks(c).length === 0);
529
+ if (clear === void 0)
530
+ throw new Error(
531
+ `RouteLine: the label "${words}" has no clear side, in field units: ${candidates.map((c) => `${c.side} ${blocks(c).join(" and ")}`).join("; ")}. Move its point, or what it meets, apart`
532
+ );
533
+ placed.push({ what: `the label "${words}"`, box: clear.box });
534
+ return clear;
535
+ });
536
+ }
513
537
  function RouteLine({
514
538
  points,
515
539
  tone,
@@ -535,19 +559,16 @@ function RouteLine({
535
559
  if (layer === "labels") {
536
560
  if (!labelled || field.ground === null) return null;
537
561
  const halo = STROKE[field.ground];
538
- const gap = labelSize * 1.1;
562
+ const places = routeLabelPlaces(points, labelSize, field);
539
563
  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";
564
+ const place = places[i];
565
+ if (!p.label || !place) return null;
545
566
  return /* @__PURE__ */ jsx2(
546
567
  "text",
547
568
  {
548
- x: lx,
549
- y: ly,
550
- textAnchor: anchor,
569
+ x: place.x,
570
+ y: place.y,
571
+ textAnchor: place.anchor,
551
572
  fontSize: labelSize,
552
573
  fontWeight: 600,
553
574
  strokeWidth: labelSize * LABEL_HALO,
@@ -759,13 +780,15 @@ var HEIGHT2 = {
759
780
  fold: "lg:min-h-[min(calc(100svh-4rem),56rem)]",
760
781
  tall: "lg:min-h-[46rem]"
761
782
  };
783
+ var CLIP_ABOVE = "[clip-path:inset(0_0_-100vh_0)]";
784
+ var COPY_CONTAINER = "lg:[container-type:inline-size] lg:[container-name:hero-copy]";
762
785
  function HeroScene({
763
786
  height,
764
787
  subject,
765
788
  copySide = "start",
766
789
  children
767
790
  }) {
768
- return /* @__PURE__ */ jsx5("div", { "data-hero-scene": "", className: "overflow-x-clip", children: /* @__PURE__ */ jsxs3(
791
+ return /* @__PURE__ */ jsx5("div", { "data-hero-scene": "", className: cn("overflow-x-clip", CLIP_ABOVE), children: /* @__PURE__ */ jsxs3(
769
792
  "div",
770
793
  {
771
794
  className: cn(
@@ -779,6 +802,7 @@ function HeroScene({
779
802
  "data-hero-copy": "",
780
803
  className: cn(
781
804
  "relative z-10 flex min-w-0 flex-col items-start justify-center gap-6 py-10 md:py-14",
805
+ COPY_CONTAINER,
782
806
  CONTENT_GUTTER,
783
807
  copySide === "end" && "lg:order-2"
784
808
  ),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caqophony/ui",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Generic, optional React UI reuse library (Gossamer tokens). Centrally maintained by Caqophony.",
5
5
  "license": "MIT",
6
6
  "author": "Caqophony",
package/styles.css CHANGED
@@ -289,6 +289,12 @@
289
289
  line-height: 1.5;
290
290
  }
291
291
 
292
+ @container hero-copy (width > 0px) {
293
+ .text-display-poster {
294
+ font-size: min(clamp(3rem, 1.05rem + 8vw, 7rem), calc(100cqi * 7 / 33.5));
295
+ }
296
+ }
297
+
292
298
  [data-page-surface='marketing'] {
293
299
  --page-bg: var(--background);
294
300
  --section-band: color-mix(in srgb, hsl(var(--foreground)) 8%, hsl(var(--background)));