reframe-video 0.1.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/assets/sfx/LICENSE.md +2 -1
  2. package/assets/sfx/bong_001.ogg +0 -0
  3. package/assets/sfx/click_001.ogg +0 -0
  4. package/assets/sfx/confirmation_002.ogg +0 -0
  5. package/assets/sfx/confirmation_003.ogg +0 -0
  6. package/assets/sfx/confirmation_004.ogg +0 -0
  7. package/assets/sfx/footstep_001.ogg +0 -0
  8. package/assets/sfx/footstep_002.ogg +0 -0
  9. package/assets/sfx/footstep_003.ogg +0 -0
  10. package/assets/sfx/glass_001.ogg +0 -0
  11. package/assets/sfx/maximize_001.ogg +0 -0
  12. package/assets/sfx/maximize_002.ogg +0 -0
  13. package/assets/sfx/maximize_005.ogg +0 -0
  14. package/assets/sfx/maximize_009.ogg +0 -0
  15. package/assets/sfx/open_001.ogg +0 -0
  16. package/assets/sfx/pluck_001.ogg +0 -0
  17. package/assets/sfx/pluck_002.ogg +0 -0
  18. package/assets/sfx/select_001.ogg +0 -0
  19. package/assets/sfx/select_002.ogg +0 -0
  20. package/assets/sfx/select_003.ogg +0 -0
  21. package/dist/bin.js +271 -49
  22. package/dist/browserEntry.js +179 -68
  23. package/dist/cli.js +445 -85
  24. package/dist/index.js +1187 -116
  25. package/dist/labels.js +606 -0
  26. package/dist/renderer-canvas.js +15 -0
  27. package/dist/trace-cli.js +9 -9
  28. package/dist/types/audio.d.ts +9 -0
  29. package/dist/types/characterPreset.d.ts +39 -0
  30. package/dist/types/compile.d.ts +1 -0
  31. package/dist/types/compose.d.ts +18 -2
  32. package/dist/types/composeComposition.d.ts +27 -0
  33. package/dist/types/devicePreset.d.ts +65 -0
  34. package/dist/types/dsl.d.ts +12 -1
  35. package/dist/types/evaluate.d.ts +32 -0
  36. package/dist/types/figure.d.ts +32 -0
  37. package/dist/types/index.d.ts +9 -3
  38. package/dist/types/interpolate.d.ts +3 -2
  39. package/dist/types/ir.d.ts +68 -0
  40. package/dist/types/motionOps.d.ts +36 -0
  41. package/dist/types/path.d.ts +7 -3
  42. package/dist/types/rig.d.ts +87 -0
  43. package/dist/types/validate.d.ts +4 -1
  44. package/guides/edsl-guide.md +54 -1
  45. package/guides/regen-contract.md +11 -0
  46. package/package.json +1 -1
  47. package/preview/index.html +56 -3
  48. package/preview/src/main.ts +1132 -46
  49. package/preview/src/panel.ts +478 -8
  50. package/preview/src/store.ts +323 -6
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // ../core/src/ir.ts
2
+ var DEFAULT_CROSSFADE = 0.5;
2
3
  var DEFAULT_TO_DURATION = 0.5;
3
4
  var DEFAULT_TWEEN_DURATION = 0.5;
4
5
  var DEFAULT_MOTIONPATH_DURATION = 1;
@@ -14,40 +15,62 @@ function locate(segCount, u) {
14
15
  return { i, t: scaled - i };
15
16
  }
16
17
  function controls(points, closed, i) {
17
- const n = points.length;
18
+ const n3 = points.length;
18
19
  const at = (k) => {
19
- if (closed) return points[(k % n + n) % n];
20
- return points[Math.max(0, Math.min(n - 1, k))];
20
+ if (closed) return points[(k % n3 + n3) % n3];
21
+ return points[Math.max(0, Math.min(n3 - 1, k))];
21
22
  };
22
23
  return [at(i - 1), at(i), at(i + 1), at(i + 2)];
23
24
  }
24
25
  function segCountOf(points, closed) {
25
- const n = points.length;
26
- if (n < 2) return 0;
27
- return closed ? n : n - 1;
28
- }
29
- function pathPoint(points, closed, u) {
30
- const n = points.length;
31
- if (n === 0) return [0, 0];
32
- if (n === 1) return [points[0][0], points[0][1]];
26
+ const n3 = points.length;
27
+ if (n3 < 2) return 0;
28
+ return closed ? n3 : n3 - 1;
29
+ }
30
+ function pathPoint(points, closed, u, curviness = 1) {
31
+ const n3 = points.length;
32
+ if (n3 === 0) return [0, 0];
33
+ if (n3 === 1) return [points[0][0], points[0][1]];
33
34
  const segs = segCountOf(points, closed);
34
35
  const { i, t } = locate(segs, u);
35
36
  const [p0, p1, p2, p3] = controls(points, closed, i);
36
37
  const t2 = t * t;
37
38
  const t3 = t2 * t;
38
- const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
39
- return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
39
+ if (curviness === 1) {
40
+ const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
41
+ return [f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])];
42
+ }
43
+ const h00 = 2 * t3 - 3 * t2 + 1;
44
+ const h10 = t3 - 2 * t2 + t;
45
+ const h01 = -2 * t3 + 3 * t2;
46
+ const h11 = t3 - t2;
47
+ const k = curviness * 0.5;
48
+ const H = (a, b, c, d) => h00 * b + h10 * k * (c - a) + h01 * c + h11 * k * (d - b);
49
+ return [H(p0[0], p1[0], p2[0], p3[0]), H(p0[1], p1[1], p2[1], p3[1])];
40
50
  }
41
- function pathTangentAngle(points, closed, u) {
42
- const n = points.length;
43
- if (n < 2) return 0;
51
+ function pathTangentAngle(points, closed, u, curviness = 1) {
52
+ const n3 = points.length;
53
+ if (n3 < 2) return 0;
44
54
  const segs = segCountOf(points, closed);
45
55
  const { i, t } = locate(segs, u);
46
56
  const [p0, p1, p2, p3] = controls(points, closed, i);
47
57
  const t2 = t * t;
48
- const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
49
- const dx = d(p0[0], p1[0], p2[0], p3[0]);
50
- const dy = d(p0[1], p1[1], p2[1], p3[1]);
58
+ let dx;
59
+ let dy;
60
+ if (curviness === 1) {
61
+ const d = (a, b, c, e) => 0.5 * (-a + c + 2 * (2 * a - 5 * b + 4 * c - e) * t + 3 * (-a + 3 * b - 3 * c + e) * t2);
62
+ dx = d(p0[0], p1[0], p2[0], p3[0]);
63
+ dy = d(p0[1], p1[1], p2[1], p3[1]);
64
+ } else {
65
+ const g00 = 6 * t2 - 6 * t;
66
+ const g10 = 3 * t2 - 4 * t + 1;
67
+ const g01 = -6 * t2 + 6 * t;
68
+ const g11 = 3 * t2 - 2 * t;
69
+ const k = curviness * 0.5;
70
+ const D = (a, b, c, e) => g00 * b + g10 * k * (c - a) + g01 * c + g11 * k * (e - b);
71
+ dx = D(p0[0], p1[0], p2[0], p3[0]);
72
+ dy = D(p0[1], p1[1], p2[1], p3[1]);
73
+ }
51
74
  if (dx === 0 && dy === 0) return 0;
52
75
  return Math.atan2(dy, dx) * 180 / Math.PI;
53
76
  }
@@ -124,8 +147,8 @@ function compileScene(ir) {
124
147
  const currentValue = (target, prop) => {
125
148
  const v = current.get(key(target, prop));
126
149
  if (v !== void 0) return v;
127
- if (prop === "opacity" || prop === "scale" || prop === "progress") return 1;
128
- if (prop === "rotation") return 0;
150
+ if (prop === "opacity" || prop === "scale" || prop === "progress" || prop === "scaleX" || prop === "scaleY") return 1;
151
+ if (prop === "rotation" || prop === "skewX" || prop === "skewY") return 0;
129
152
  throw new Error(`cannot animate "${prop}" of "${target}": no base value to start from`);
130
153
  };
131
154
  const labelTimes = /* @__PURE__ */ new Map();
@@ -228,16 +251,17 @@ function compileScene(ir) {
228
251
  const duration = tl.duration ?? DEFAULT_MOTIONPATH_DURATION;
229
252
  const points = tl.points;
230
253
  const closed = tl.closed ?? false;
254
+ const curviness = tl.curviness ?? 1;
231
255
  const autoRotate = tl.autoRotate ?? false;
232
256
  const rotateOffset = tl.rotateOffset ?? 0;
233
257
  let list = motionPaths.get(tl.target);
234
258
  if (!list) motionPaths.set(tl.target, list = []);
235
- list.push({ t0: start, t1: start + duration, points, closed, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
259
+ list.push({ t0: start, t1: start + duration, points, closed, curviness, autoRotate, rotateOffset, ...tl.ease !== void 0 && { ease: tl.ease } });
236
260
  if (points.length > 0) {
237
- const [ex, ey] = pathPoint(points, closed, 1);
261
+ const [ex, ey] = pathPoint(points, closed, 1, curviness);
238
262
  current.set(key(tl.target, "x"), ex);
239
263
  current.set(key(tl.target, "y"), ey);
240
- if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1) + rotateOffset);
264
+ if (autoRotate) current.set(key(tl.target, "rotation"), pathTangentAngle(points, closed, 1, curviness) + rotateOffset);
241
265
  }
242
266
  return start + duration;
243
267
  }
@@ -284,7 +308,7 @@ function compileScene(ir) {
284
308
  }
285
309
 
286
310
  // ../core/src/validate.ts
287
- var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "anchor"];
311
+ var COMMON_PROPS = ["x", "y", "opacity", "rotation", "scale", "scaleX", "scaleY", "skewX", "skewY", "anchor"];
288
312
  var PROPS_BY_TYPE = {
289
313
  rect: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth", "radius"],
290
314
  ellipse: [...COMMON_PROPS, "width", "height", "fill", "stroke", "strokeWidth"],
@@ -312,7 +336,18 @@ function validateScene(ir) {
312
336
  problems.push(`duplicate node id "${node.id}" \u2014 every node id must be unique`);
313
337
  }
314
338
  nodeById.set(node.id, node);
315
- if (node.type === "group") collect(node.children);
339
+ if (node.type === "group") {
340
+ const clip = node.props.clip;
341
+ if (clip) {
342
+ if (clip.kind !== "rect" && clip.kind !== "ellipse") {
343
+ problems.push(`group "${node.id}" clip: unknown kind "${clip.kind}" \u2014 use "rect" or "ellipse"`);
344
+ }
345
+ if (!(clip.width > 0) || !(clip.height > 0)) {
346
+ problems.push(`group "${node.id}" clip: width and height must be > 0`);
347
+ }
348
+ }
349
+ collect(node.children);
350
+ }
316
351
  }
317
352
  };
318
353
  collect(ir.nodes);
@@ -395,6 +430,9 @@ function validateScene(ir) {
395
430
  if (tl.duration !== void 0 && tl.duration <= 0) {
396
431
  problems.push(`${path2}: motionPath "${tl.target}" duration must be > 0`);
397
432
  }
433
+ if (tl.curviness !== void 0 && tl.curviness < 0) {
434
+ problems.push(`${path2}: motionPath "${tl.target}" curviness must be >= 0`);
435
+ }
398
436
  break;
399
437
  }
400
438
  case "wait":
@@ -413,6 +451,13 @@ function validateScene(ir) {
413
451
  if (tl.scale !== void 0 && tl.scale <= 0) {
414
452
  problems.push(`${path2}: beat "${tl.name}" scale must be > 0`);
415
453
  }
454
+ for (const id of tl.nodes ?? []) {
455
+ if (!nodeById.has(id)) {
456
+ problems.push(
457
+ `${path2}: beat "${tl.name}" owns unknown node "${id}" \u2014 known ids: ${[...nodeById.keys()].join(", ")}`
458
+ );
459
+ }
460
+ }
416
461
  tl.children.forEach((c, i) => checkTimeline(c, `${path2}.beat(${tl.name})[${i}]`));
417
462
  break;
418
463
  }
@@ -453,6 +498,36 @@ function validateScene(ir) {
453
498
  }
454
499
  if (problems.length > 0) throw new SceneValidationError(problems);
455
500
  }
501
+ var TRANSITIONS = ["cut", "crossfade"];
502
+ function validateComposition(comp) {
503
+ const problems = [];
504
+ if (comp.scenes.length === 0) problems.push("composition has no scenes");
505
+ const seen = /* @__PURE__ */ new Set();
506
+ for (const [i, entry] of comp.scenes.entries()) {
507
+ const where = `scenes[${i}]`;
508
+ try {
509
+ validateScene(entry.scene);
510
+ } catch (err) {
511
+ if (err instanceof SceneValidationError) {
512
+ for (const p of err.problems) problems.push(`${where} (scene "${entry.scene.id}"): ${p}`);
513
+ } else throw err;
514
+ }
515
+ if (seen.has(entry.scene.id)) {
516
+ problems.push(`${where}: duplicate scene id "${entry.scene.id}" \u2014 scene ids must be unique in a composition`);
517
+ }
518
+ seen.add(entry.scene.id);
519
+ if (entry.transition !== void 0 && !TRANSITIONS.includes(entry.transition)) {
520
+ problems.push(`${where}: unknown transition "${entry.transition}" \u2014 valid: ${TRANSITIONS.join(", ")}`);
521
+ }
522
+ if (typeof entry.at === "string" && Number.isNaN(Number(entry.at))) {
523
+ problems.push(`${where}: "at" string "${entry.at}" is not a number (use "-0.5"/"+0.5" or a number)`);
524
+ }
525
+ if (typeof entry.at === "number" && entry.at < 0) {
526
+ problems.push(`${where}: absolute "at" must be >= 0`);
527
+ }
528
+ }
529
+ if (problems.length > 0) throw new SceneValidationError(problems);
530
+ }
456
531
 
457
532
  // ../core/src/dsl.ts
458
533
  function scene(input) {
@@ -463,6 +538,11 @@ function scene(input) {
463
538
  }
464
539
  return ir;
465
540
  }
541
+ function composition(input) {
542
+ const ir = { version: 1, ...input };
543
+ validateComposition(ir);
544
+ return ir;
545
+ }
466
546
  function rect(props) {
467
547
  const { id, ...rest } = props;
468
548
  return { type: "rect", id, props: rest };
@@ -522,11 +602,47 @@ function wiggle(target, prop, params, window = {}) {
522
602
  return { target, prop, ...window, behavior: { kind: "named", name: "wiggle", params } };
523
603
  }
524
604
 
605
+ // ../core/src/composeComposition.ts
606
+ function compileComposition(comp) {
607
+ const scenes = [];
608
+ let prevEnd = 0;
609
+ comp.scenes.forEach((entry, i) => {
610
+ const compiled = compileScene(entry.scene);
611
+ const duration2 = compiled.duration;
612
+ const transition = entry.transition ?? "cut";
613
+ const append = i === 0 ? 0 : prevEnd;
614
+ let start;
615
+ if (typeof entry.at === "number") {
616
+ start = entry.at;
617
+ } else if (typeof entry.at === "string") {
618
+ start = append + Number(entry.at);
619
+ } else if (transition === "crossfade" && i > 0) {
620
+ start = append - DEFAULT_CROSSFADE;
621
+ } else {
622
+ start = append;
623
+ }
624
+ start = Math.max(0, start);
625
+ const overlap = i > 0 ? Math.max(0, prevEnd - start) : 0;
626
+ scenes.push({ id: entry.scene.id, scene: entry.scene, compiled, start, duration: duration2, transition, overlap });
627
+ prevEnd = start + duration2;
628
+ });
629
+ const duration = scenes.reduce((max, s) => Math.max(max, s.start + s.duration), 0);
630
+ return { ir: comp, scenes, duration };
631
+ }
632
+
525
633
  // ../core/src/compose.ts
526
634
  var SCENE_PATCHABLE = ["background", "duration", "fps"];
527
635
  function composeScene(base, ...overlays) {
528
636
  const ir = structuredClone(base);
529
637
  const report = { applied: [], orphans: [], warnings: [] };
638
+ const baseNodeIds = /* @__PURE__ */ new Set();
639
+ const collectBase = (nodes) => {
640
+ for (const node of nodes) {
641
+ baseNodeIds.add(node.id);
642
+ if (node.type === "group") collectBase(node.children);
643
+ }
644
+ };
645
+ collectBase(base.nodes);
530
646
  overlays.forEach((overlay, index) => {
531
647
  const layer = overlay.name ?? `overlay-${index}`;
532
648
  if (overlay.target !== void 0 && overlay.target !== ir.id) {
@@ -534,12 +650,12 @@ function composeScene(base, ...overlays) {
534
650
  `${layer}: authored against scene "${overlay.target}" but composing onto "${ir.id}"`
535
651
  );
536
652
  }
537
- applyOverlay(ir, overlay, layer, report);
653
+ applyOverlay(ir, overlay, layer, report, baseNodeIds);
538
654
  });
539
655
  validateScene(ir);
540
656
  return { ir, report };
541
657
  }
542
- function applyOverlay(ir, overlay, layer, report) {
658
+ function applyOverlay(ir, overlay, layer, report, baseNodeIds) {
543
659
  const nodeById = /* @__PURE__ */ new Map();
544
660
  const collect = (nodes) => {
545
661
  for (const node of nodes) {
@@ -654,7 +770,7 @@ function applyOverlay(ir, overlay, layer, report) {
654
770
  to: ["duration", "ease", "stagger"],
655
771
  tween: ["duration", "ease"],
656
772
  wait: ["duration"],
657
- motionPath: ["points", "duration", "ease"],
773
+ motionPath: ["points", "duration", "ease", "curviness", "autoRotate"],
658
774
  beat: ["at", "gap", "scale", "duration", "order"]
659
775
  };
660
776
  let timingPatched = false;
@@ -692,6 +808,49 @@ function applyOverlay(ir, overlay, layer, report) {
692
808
  nodeById.set(node.id, node);
693
809
  applied(`addNodes.${node.id}`, "add-node");
694
810
  }
811
+ for (const id of overlay.removeNodes ?? []) {
812
+ if (baseNodeIds.has(id)) {
813
+ orphan(
814
+ `removeNodes.${id}`,
815
+ `"${id}" is a base scene node \u2014 the scene owns it; hide it with opacity: 0 instead of removing`
816
+ );
817
+ continue;
818
+ }
819
+ const index = ir.nodes.findIndex((n3) => n3.id === id);
820
+ if (index < 0) {
821
+ orphan(
822
+ `removeNodes.${id}`,
823
+ `unknown overlay-added node "${id}" \u2014 nothing to remove`
824
+ );
825
+ continue;
826
+ }
827
+ ir.nodes.splice(index, 1);
828
+ nodeById.delete(id);
829
+ applied(`removeNodes.${id}`, "remove-node");
830
+ }
831
+ if (overlay.addTimeline && overlay.addTimeline.length > 0) {
832
+ const collectTargets = (tl, out) => {
833
+ if (tl.kind === "tween" || tl.kind === "motionPath") out.add(tl.target);
834
+ if ("children" in tl) tl.children.forEach((c) => collectTargets(c, out));
835
+ };
836
+ const valid = [];
837
+ overlay.addTimeline.forEach((frag, i) => {
838
+ const targets = /* @__PURE__ */ new Set();
839
+ collectTargets(frag, targets);
840
+ const missing = [...targets].filter((id) => !nodeById.has(id));
841
+ if (missing.length > 0) {
842
+ orphan(`addTimeline[${i}]`, `targets unknown node(s) ${missing.join(", ")} \u2014 known ids: ${knownIds()}`);
843
+ return;
844
+ }
845
+ valid.push(structuredClone(frag));
846
+ applied(`addTimeline[${i}]`, "add-timeline");
847
+ });
848
+ if (valid.length > 0) {
849
+ ir.timeline = ir.timeline ? { kind: "par", children: [ir.timeline, ...valid] } : valid.length === 1 ? valid[0] : { kind: "par", children: valid };
850
+ delete ir.duration;
851
+ ir.duration = compileScene(ir).duration;
852
+ }
853
+ }
695
854
  }
696
855
  function formatComposeReport(report) {
697
856
  const lines = [];
@@ -884,6 +1043,735 @@ function motionPreset(name, opts) {
884
1043
  }
885
1044
  }
886
1045
 
1046
+ // ../core/src/devicePreset.ts
1047
+ var DEVICE_PRESET_NAMES = ["phone", "tablet", "laptop", "browser", "watch", "monitor", "tv", "foldable", "terminal", "car"];
1048
+ var DARK = { body: "#15161C", bodyStroke: "#2A2D38", screen: "#0E0F15", detail: "#3A3D48", chrome: "#1B1D24", chromeText: "#9AA0AD" };
1049
+ var LIGHT = { body: "#E7E9EE", bodyStroke: "#C3C7D1", screen: "#FFFFFF", detail: "#AEB3C0", chrome: "#F2F3F6", chromeText: "#5B606C" };
1050
+ var SCREENS = {
1051
+ phone: { width: 352, height: 736, radius: 38 },
1052
+ tablet: { width: 544, height: 764, radius: 18 },
1053
+ laptop: { width: 840, height: 520, radius: 8, cy: -150 },
1054
+ browser: { width: 984, height: 568, radius: 6, cy: 24 },
1055
+ watch: { width: 184, height: 224, radius: 44 },
1056
+ monitor: { width: 1056, height: 600, radius: 6 },
1057
+ tv: { width: 1280, height: 720, radius: 8, cy: -24 },
1058
+ foldable: { width: 760, height: 560, radius: 20 },
1059
+ terminal: { width: 900, height: 560, radius: 6, cy: 18 },
1060
+ car: { width: 1e3, height: 520, radius: 24 }
1061
+ };
1062
+ var BOUNDS = {
1063
+ phone: { width: 392, height: 812 },
1064
+ tablet: { width: 600, height: 820 },
1065
+ laptop: { width: 1100, height: 650 },
1066
+ browser: { width: 1e3, height: 660 },
1067
+ watch: { width: 220, height: 300 },
1068
+ monitor: { width: 1120, height: 860 },
1069
+ tv: { width: 1340, height: 920 },
1070
+ foldable: { width: 800, height: 600 },
1071
+ terminal: { width: 916, height: 636 },
1072
+ car: { width: 1060, height: 600 }
1073
+ };
1074
+ var isLandscape = (name, o) => (name === "phone" || name === "tablet") && o.orientation === "landscape";
1075
+ function screenDims(name, o) {
1076
+ const d = SCREENS[name];
1077
+ const base = { cx: d.cx ?? 0, cy: d.cy ?? 0 };
1078
+ return isLandscape(name, o) ? { width: d.height, height: d.width, radius: d.radius, ...base } : { width: d.width, height: d.height, radius: d.radius, ...base };
1079
+ }
1080
+ function deviceScreen(name, opts = {}) {
1081
+ const d = screenDims(name, opts);
1082
+ return { x: 0, y: 0, width: d.width, height: d.height, radius: d.radius };
1083
+ }
1084
+ function deviceScreenCenter(name, opts = {}) {
1085
+ const d = screenDims(name, opts);
1086
+ return { x: d.cx, y: d.cy };
1087
+ }
1088
+ function deviceBounds(name, opts = {}) {
1089
+ const b = BOUNDS[name];
1090
+ return isLandscape(name, opts) ? { width: b.height, height: b.width } : { ...b };
1091
+ }
1092
+ function screenGroup(id, p, o, cx, cy, dims, content) {
1093
+ return group({ id: `${id}-screen`, x: cx, y: cy, clip: { kind: "rect", x: -dims.width / 2, y: -dims.height / 2, width: dims.width, height: dims.height, radius: dims.radius } }, [
1094
+ rect({ id: `${id}-screenbg`, x: 0, y: 0, anchor: "center", width: dims.width, height: dims.height, fill: o.screen ?? p.screen }),
1095
+ group({ id: `${id}-content`, x: 0, y: 0 }, content)
1096
+ ]);
1097
+ }
1098
+ function buildDevice(name, id, p, o, content) {
1099
+ const dims = screenDims(name, o);
1100
+ const sw = dims.width;
1101
+ const sh = dims.height;
1102
+ const screen = () => screenGroup(id, p, o, dims.cx, dims.cy, dims, content);
1103
+ switch (name) {
1104
+ case "phone":
1105
+ case "tablet": {
1106
+ const bezel = name === "phone" ? 20 : 28;
1107
+ const bodyW = sw + bezel * 2;
1108
+ const bodyH = sh + bezel * 2;
1109
+ const bodyR = name === "phone" ? 54 : 34;
1110
+ const land = isLandscape(name, o);
1111
+ const nodes = [
1112
+ rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: bodyR }),
1113
+ screen()
1114
+ ];
1115
+ if (name === "phone") {
1116
+ nodes.push(
1117
+ land ? rect({ id: `${id}-notch`, x: -sw / 2 + 16, y: 0, anchor: "center", width: 30, height: 96, fill: "#000000", radius: 15 }) : rect({ id: `${id}-notch`, x: 0, y: -sh / 2 + 16, anchor: "center", width: 96, height: 30, fill: "#000000", radius: 15 }),
1118
+ land ? rect({ id: `${id}-home`, x: sw / 2 - 4, y: 0, anchor: "center", width: 5, height: 120, fill: p.detail, radius: 3 }) : rect({ id: `${id}-home`, x: 0, y: sh / 2 - 18, anchor: "center", width: 120, height: 5, fill: p.detail, radius: 3 })
1119
+ );
1120
+ if (!land) {
1121
+ nodes.push(
1122
+ rect({ id: `${id}-pwr`, x: bodyW / 2, y: -bodyH * 0.1, anchor: "center", width: 4, height: 78, fill: p.detail, radius: 2 }),
1123
+ rect({ id: `${id}-volup`, x: -bodyW / 2, y: -bodyH * 0.16, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 }),
1124
+ rect({ id: `${id}-voldn`, x: -bodyW / 2, y: -bodyH * 0.16 + 60, anchor: "center", width: 4, height: 48, fill: p.detail, radius: 2 })
1125
+ );
1126
+ }
1127
+ } else {
1128
+ nodes.push(
1129
+ rect({ id: `${id}-camera`, x: land ? -sw / 2 - 14 : 0, y: land ? 0 : -sh / 2 - 14, anchor: "center", width: 8, height: 8, fill: p.detail, radius: 4 }),
1130
+ rect({ id: `${id}-pwr`, x: land ? -bodyW * 0.18 : bodyW * 0.18, y: land ? -bodyH / 2 : -bodyH / 2, anchor: "center", width: 60, height: 4, fill: p.detail, radius: 2 })
1131
+ );
1132
+ }
1133
+ return nodes;
1134
+ }
1135
+ case "laptop": {
1136
+ const lidTop = dims.cy - (sh + 40) / 2;
1137
+ const keyRows = [0, 1, 2, 3].map(
1138
+ (r) => rect({ id: `${id}-keys${r}`, x: 0, y: 150 + r * 11, anchor: "center", width: 640 + r * 50, height: 6, fill: p.chrome, radius: 3 })
1139
+ );
1140
+ return [
1141
+ path({ id: `${id}-base`, x: 0, y: 0, d: "M -450 140 L 450 140 L 520 196 L -520 196 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
1142
+ rect({ id: `${id}-foot-l`, x: -360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
1143
+ rect({ id: `${id}-foot-r`, x: 360, y: 198, anchor: "center", width: 70, height: 5, fill: p.detail, radius: 3 }),
1144
+ ...keyRows,
1145
+ rect({ id: `${id}-trackpad`, x: 0, y: 184, anchor: "center", width: 150, height: 8, fill: p.detail, radius: 4 }),
1146
+ rect({ id: `${id}-hinge`, x: 0, y: 134, anchor: "center", width: 900, height: 10, fill: p.detail, radius: 5 }),
1147
+ screen(),
1148
+ ellipse({ id: `${id}-webcam`, x: 0, y: lidTop + 14, anchor: "center", width: 6, height: 6, fill: p.detail }),
1149
+ rect({ id: `${id}-lid`, x: 0, y: dims.cy, anchor: "center", width: sw + 40, height: sh + 40, stroke: p.bodyStroke, strokeWidth: 2, radius: 18 })
1150
+ ];
1151
+ }
1152
+ case "browser": {
1153
+ const winW = sw + 16;
1154
+ const winH = sh + 92;
1155
+ const barY = -winH / 2 + 24;
1156
+ return [
1157
+ rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 14 }),
1158
+ ellipse({ id: `${id}-dot1`, x: -winW / 2 + 30, y: barY, anchor: "center", width: 13, height: 13, fill: "#FF5F57" }),
1159
+ ellipse({ id: `${id}-dot2`, x: -winW / 2 + 54, y: barY, anchor: "center", width: 13, height: 13, fill: "#FEBC2E" }),
1160
+ ellipse({ id: `${id}-dot3`, x: -winW / 2 + 78, y: barY, anchor: "center", width: 13, height: 13, fill: "#28C840" }),
1161
+ // an active tab tucked under the lights
1162
+ rect({ id: `${id}-tab`, x: -winW / 2 + 230, y: barY, anchor: "center", width: 190, height: 30, fill: o.screen ?? p.screen, radius: 8 }),
1163
+ text({ id: `${id}-tabtext`, x: -winW / 2 + 156, y: barY, anchor: "center-left", content: "Overview", fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
1164
+ rect({ id: `${id}-urlpill`, x: 96, y: barY, anchor: "center", width: 700, height: 26, fill: o.screen ?? p.screen, stroke: p.bodyStroke, strokeWidth: 1, radius: 13 }),
1165
+ rect({ id: `${id}-lock`, x: 96 - 330, y: barY, anchor: "center", width: 8, height: 10, fill: p.chromeText, radius: 2 }),
1166
+ text({ id: `${id}-urltext`, x: 96 - 312, y: barY, anchor: "center-left", content: urlText(o.url), fontFamily: "Inter", fontSize: 14, fill: p.chromeText }),
1167
+ screen()
1168
+ ];
1169
+ }
1170
+ case "watch": {
1171
+ const bw = sw + 36;
1172
+ const bh = sh + 36;
1173
+ return [
1174
+ // straps (drawn behind the body) flaring out top & bottom
1175
+ path({ id: `${id}-bandtop`, x: 0, y: -bh / 2 + 4, d: "M -78 0 L 78 0 L 64 -86 L -64 -86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
1176
+ path({ id: `${id}-bandbot`, x: 0, y: bh / 2 - 4, d: "M -78 0 L 78 0 L 64 86 L -64 86 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 }),
1177
+ rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bw, height: bh, fill: p.body, stroke: p.bodyStroke, strokeWidth: 3, radius: 60 }),
1178
+ screen(),
1179
+ rect({ id: `${id}-crown`, x: bw / 2, y: -20, anchor: "center", width: 14, height: 40, fill: p.detail, radius: 6 }),
1180
+ rect({ id: `${id}-button`, x: bw / 2 - 2, y: 40, anchor: "center", width: 8, height: 34, fill: p.detail, radius: 4 })
1181
+ ];
1182
+ }
1183
+ case "monitor": {
1184
+ const panelW = sw + 44;
1185
+ const panelH = sh + 60;
1186
+ return [
1187
+ rect({ id: `${id}-panel`, x: 0, y: 0, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 16 }),
1188
+ screen(),
1189
+ ellipse({ id: `${id}-led`, x: panelW / 2 - 26, y: panelH / 2 - 16, anchor: "center", width: 6, height: 6, fill: "#28C840" }),
1190
+ rect({ id: `${id}-neck`, x: 0, y: panelH / 2 + 60, anchor: "center", width: 60, height: 120, fill: p.body }),
1191
+ path({ id: `${id}-stand`, x: 0, y: panelH / 2 + 60, d: "M -160 50 L 160 50 L 220 80 L -220 80 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
1192
+ ];
1193
+ }
1194
+ case "tv": {
1195
+ const panelW = sw + 44;
1196
+ const panelH = sh + 48;
1197
+ const panelBottom = dims.cy + panelH / 2;
1198
+ return [
1199
+ rect({ id: `${id}-panel`, x: 0, y: dims.cy, anchor: "center", width: panelW, height: panelH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 12 }),
1200
+ screen(),
1201
+ ellipse({ id: `${id}-brand`, x: 0, y: panelBottom - 12, anchor: "center", width: 6, height: 6, fill: p.detail }),
1202
+ rect({ id: `${id}-neck`, x: 0, y: panelBottom + 48, anchor: "center", width: 64, height: 96, fill: p.body }),
1203
+ path({ id: `${id}-stand`, x: 0, y: panelBottom + 96, d: "M -210 0 L 210 0 L 270 34 L -270 34 Z", fill: p.body, stroke: p.bodyStroke, strokeWidth: 2 })
1204
+ ];
1205
+ }
1206
+ case "foldable": {
1207
+ const bodyW = sw + 40;
1208
+ const bodyH = sh + 40;
1209
+ return [
1210
+ rect({ id: `${id}-hinge-l`, x: -bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
1211
+ rect({ id: `${id}-hinge-r`, x: bodyW / 2, y: 0, anchor: "center", width: 8, height: bodyH * 0.5, fill: p.detail, radius: 4 }),
1212
+ rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 28 }),
1213
+ screen(),
1214
+ rect({ id: `${id}-crease`, x: 0, y: 0, anchor: "center", width: 4, height: sh, fill: p.bodyStroke, radius: 2, opacity: 0.5 }),
1215
+ ellipse({ id: `${id}-cam1`, x: -10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail }),
1216
+ ellipse({ id: `${id}-cam2`, x: 10, y: -sh / 2 + 18, anchor: "center", width: 8, height: 8, fill: p.detail })
1217
+ ];
1218
+ }
1219
+ case "terminal": {
1220
+ const winW = sw + 16;
1221
+ const winH = sh + 76;
1222
+ return [
1223
+ rect({ id: `${id}-win`, x: 0, y: 0, anchor: "center", width: winW, height: winH, fill: p.chrome, stroke: p.bodyStroke, strokeWidth: 1.5, radius: 12 }),
1224
+ ellipse({ id: `${id}-dot1`, x: -winW / 2 + 28, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FF5F57" }),
1225
+ ellipse({ id: `${id}-dot2`, x: -winW / 2 + 50, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#FEBC2E" }),
1226
+ ellipse({ id: `${id}-dot3`, x: -winW / 2 + 72, y: -winH / 2 + 22, anchor: "center", width: 12, height: 12, fill: "#28C840" }),
1227
+ rect({ id: `${id}-tab`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", width: 130, height: 24, fill: o.screen ?? p.screen, radius: 6 }),
1228
+ text({ id: `${id}-title`, x: -winW / 2 + 170, y: -winH / 2 + 22, anchor: "center", content: urlText(o.url ?? "zsh"), fontFamily: "Inter", fontSize: 13, fill: p.chromeText }),
1229
+ screen()
1230
+ ];
1231
+ }
1232
+ case "car": {
1233
+ const bodyW = sw + 60;
1234
+ const bodyH = sh + 60;
1235
+ return [
1236
+ rect({ id: `${id}-body`, x: 0, y: 0, anchor: "center", width: bodyW, height: bodyH, fill: p.body, stroke: p.bodyStroke, strokeWidth: 2, radius: 40 }),
1237
+ ellipse({ id: `${id}-knob`, x: -bodyW / 2 + 18, y: 0, anchor: "center", width: 22, height: 22, fill: p.body, stroke: p.detail, strokeWidth: 3 }),
1238
+ screen(),
1239
+ ellipse({ id: `${id}-btn1`, x: -44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
1240
+ ellipse({ id: `${id}-btn2`, x: 0, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail }),
1241
+ ellipse({ id: `${id}-btn3`, x: 44, y: sh / 2 + 16, anchor: "center", width: 12, height: 12, fill: p.detail })
1242
+ ];
1243
+ }
1244
+ }
1245
+ }
1246
+ var urlText = (url) => {
1247
+ const u = url ?? "reframe.video";
1248
+ return u.length > 70 ? `${u.slice(0, 67)}\u2026` : u;
1249
+ };
1250
+ function devicePreset(name, opts = {}) {
1251
+ const id = opts.id ?? "device";
1252
+ const p = opts.color === "light" ? LIGHT : DARK;
1253
+ const children = buildDevice(name, id, p, opts, opts.content ?? []);
1254
+ return group(
1255
+ {
1256
+ id,
1257
+ x: opts.x ?? 0,
1258
+ y: opts.y ?? 0,
1259
+ ...opts.scale !== void 0 && opts.scale !== 1 && { scale: opts.scale },
1260
+ ...opts.opacity !== void 0 && opts.opacity !== 1 && { opacity: opts.opacity }
1261
+ },
1262
+ children
1263
+ );
1264
+ }
1265
+
1266
+ // ../core/src/rig.ts
1267
+ var DEFAULT_LINE = "#FFE3D2";
1268
+ var DEFAULT_FILL = "#0E1424";
1269
+ var LINE_W = 5;
1270
+ var GLOW_W = 16;
1271
+ var K = 0.5523;
1272
+ var n = (v) => Number(v.toFixed(2));
1273
+ function capsulePath(hw, len) {
1274
+ const yT = hw;
1275
+ const yB = Math.max(hw, len - hw);
1276
+ const k = hw * K;
1277
+ return `M ${n(-hw)} ${n(yT)} C ${n(-hw)} ${n(yT - k)} ${n(-k)} ${n(yT - hw)} 0 ${n(yT - hw)} C ${n(k)} ${n(yT - hw)} ${n(hw)} ${n(yT - k)} ${n(hw)} ${n(yT)} L ${n(hw)} ${n(yB)} C ${n(hw)} ${n(yB + k)} ${n(k)} ${n(yB + hw)} 0 ${n(yB + hw)} C ${n(-k)} ${n(yB + hw)} ${n(-hw)} ${n(yB + k)} ${n(-hw)} ${n(yB)} Z`;
1278
+ }
1279
+ function ovalPath(a, b, cx = 0, cy = 0) {
1280
+ const ka = n(a * K), kb = n(b * K);
1281
+ const t = n(cy - b), bo = n(cy + b), c = n(cy), A = n(a), L = n(-a), X = n(cx);
1282
+ return `M ${X} ${t} C ${n(cx + ka)} ${t} ${n(cx + A)} ${n(cy - kb)} ${n(cx + A)} ${c} C ${n(cx + A)} ${n(cy + kb)} ${n(cx + ka)} ${bo} ${X} ${bo} C ${n(cx - ka)} ${bo} ${n(cx + L)} ${n(cy + kb)} ${n(cx + L)} ${c} C ${n(cx + L)} ${n(cy - kb)} ${n(cx - ka)} ${t} ${X} ${t} Z`;
1283
+ }
1284
+ function boneShape(jointId, bone, o) {
1285
+ if (bone.shape) return bone.shape;
1286
+ const len = bone.length ?? 0;
1287
+ if (len <= 0) return [];
1288
+ const d = capsulePath((bone.width ?? 20) / 2, len);
1289
+ const nodes = [];
1290
+ if (o.glow) nodes.push(path({ id: `${jointId}-glow`, d, x: 0, y: 0, fill: "none", stroke: o.glow, strokeWidth: GLOW_W, opacity: 0.18 }));
1291
+ nodes.push(path({ id: `${jointId}-shape`, d, x: 0, y: 0, fill: o.fill, stroke: o.color, strokeWidth: LINE_W }));
1292
+ return nodes;
1293
+ }
1294
+ function buildBone(bone, id, o) {
1295
+ const jointId = `${id}-${bone.name}`;
1296
+ return group(
1297
+ { id: jointId, x: bone.at[0], y: bone.at[1], rotation: bone.rotation ?? 0 },
1298
+ [...boneShape(jointId, bone, o), ...(bone.children ?? []).map((c) => buildBone(c, id, o))]
1299
+ );
1300
+ }
1301
+ function rig(root, opts = {}) {
1302
+ const id = opts.id ?? "rig";
1303
+ const o = { color: opts.color ?? DEFAULT_LINE, fill: opts.fill ?? DEFAULT_FILL, glow: opts.glow };
1304
+ return group(
1305
+ { id, x: opts.x ?? 0, y: opts.y ?? 0, scale: opts.scale ?? 1, opacity: opts.opacity ?? 1 },
1306
+ [buildBone(root, id, o)]
1307
+ );
1308
+ }
1309
+ function rigPose(id, pose) {
1310
+ const out = {};
1311
+ for (const [name, deg] of Object.entries(pose)) out[`${id}-${name}`] = { rotation: deg };
1312
+ return out;
1313
+ }
1314
+ function poseTo(id, pose, opts = {}) {
1315
+ const tweens = Object.entries(pose).map(
1316
+ ([name, deg]) => tween(`${id}-${name}`, { rotation: deg }, { duration: opts.duration ?? 0.5, ease: opts.ease ?? "easeInOutCubic" })
1317
+ );
1318
+ return opts.stagger ? stagger(opts.stagger, ...tweens) : par(...tweens);
1319
+ }
1320
+ function ikReach(upper, lower, dx, dy, flip = false) {
1321
+ const D = Math.hypot(dx, dy);
1322
+ const cos2 = Math.max(-1, Math.min(1, (D * D - upper * upper - lower * lower) / (2 * upper * lower)));
1323
+ const theta2 = (flip ? -1 : 1) * Math.acos(cos2);
1324
+ const vx = -lower * Math.sin(theta2);
1325
+ const vy = upper + lower * Math.cos(theta2);
1326
+ const theta1 = Math.atan2(dy, dx) - Math.atan2(vy, vx);
1327
+ const deg = (r) => r * 180 / Math.PI;
1328
+ return [deg(theta1), deg(theta2)];
1329
+ }
1330
+ function humanoid(opts = {}) {
1331
+ const line2 = opts.color ?? DEFAULT_LINE;
1332
+ const fill = opts.fill ?? DEFAULT_FILL;
1333
+ const glow = opts.glow;
1334
+ const blob = (jid, a, b, cy) => {
1335
+ const d = ovalPath(a, b, 0, cy);
1336
+ const nodes = [];
1337
+ if (glow) nodes.push(path({ id: `${jid}-glow`, d, x: 0, y: 0, fill: "none", stroke: glow, strokeWidth: GLOW_W, opacity: 0.18 }));
1338
+ nodes.push(path({ id: `${jid}-shape`, d, x: 0, y: 0, fill, stroke: line2, strokeWidth: LINE_W }));
1339
+ return nodes;
1340
+ };
1341
+ const id = opts.id ?? "rig";
1342
+ const root = {
1343
+ name: "chest",
1344
+ at: [0, 0],
1345
+ shape: blob(`${id}-chest`, 44, 62, 22),
1346
+ children: [
1347
+ { name: "head", at: [0, -42], rotation: 0, shape: blob(`${id}-head`, 40, 42, -34) },
1348
+ { name: "armUpperL", at: [-42, -20], length: 60, width: 20, rotation: 10, children: [
1349
+ { name: "armLowerL", at: [0, 60], length: 56, width: 16, rotation: 8 }
1350
+ ] },
1351
+ { name: "armUpperR", at: [42, -20], length: 60, width: 20, rotation: -10, children: [
1352
+ { name: "armLowerR", at: [0, 60], length: 56, width: 16, rotation: -8 }
1353
+ ] },
1354
+ { name: "legUpperL", at: [-20, 76], length: 76, width: 26, rotation: 3, children: [
1355
+ { name: "legLowerL", at: [0, 76], length: 72, width: 22, rotation: -2 }
1356
+ ] },
1357
+ { name: "legUpperR", at: [20, 76], length: 76, width: 26, rotation: -3, children: [
1358
+ { name: "legLowerR", at: [0, 76], length: 72, width: 22, rotation: 2 }
1359
+ ] }
1360
+ ]
1361
+ };
1362
+ return rig(root, opts);
1363
+ }
1364
+
1365
+ // ../core/src/characterPreset.ts
1366
+ var CHARACTER_PRESET_NAMES = ["walk", "run", "jump", "dance", "wave", "cheer"];
1367
+ var THIGH = 76;
1368
+ var SHIN = 72;
1369
+ var clamp012 = (x) => Math.max(0, Math.min(1, x));
1370
+ function makeRng2(seed) {
1371
+ let a = seed >>> 0 || 2654435769;
1372
+ return () => {
1373
+ a = a + 1831565813 | 0;
1374
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
1375
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
1376
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1377
+ };
1378
+ }
1379
+ var dur2 = (base, sp) => base / sp;
1380
+ function ctx2(o) {
1381
+ const rand = makeRng2((o.seed ?? 0) + 1);
1382
+ return {
1383
+ g: o.target,
1384
+ label: o.label,
1385
+ e: clamp012(o.energy ?? 0.5),
1386
+ sp: Math.max(0.25, o.speed ?? 1),
1387
+ cycles: Math.max(1, Math.round(o.cycles ?? 4)),
1388
+ facing: o.facing ?? 1,
1389
+ at: o.at ?? [0, 0],
1390
+ travel: o.travel,
1391
+ rand,
1392
+ jit: (amp) => (rand() - 0.5) * 2 * amp
1393
+ };
1394
+ }
1395
+ var round = (v) => Math.round(v * 1e3) / 1e3;
1396
+ function footPos(p, stride, lift) {
1397
+ p = (p % 1 + 1) % 1;
1398
+ if (p < 0.5) {
1399
+ const u2 = p / 0.5;
1400
+ return [stride * (1 - 2 * u2), 138];
1401
+ }
1402
+ const u = (p - 0.5) / 0.5;
1403
+ return [-stride + 2 * stride * u, 138 - Math.sin(Math.PI * u) * lift];
1404
+ }
1405
+ function gaitPose(ph, stride, lift, armSwing, facing) {
1406
+ const fl = footPos(ph, stride, lift);
1407
+ const fr = footPos(ph + 0.5, stride, lift);
1408
+ const [hipL, kneeL] = ikReach(THIGH, SHIN, facing * fl[0], fl[1], facing < 0);
1409
+ const [hipR, kneeR] = ikReach(THIGH, SHIN, facing * fr[0], fr[1], facing < 0);
1410
+ const swing = Math.cos(2 * Math.PI * ph);
1411
+ return {
1412
+ legUpperL: round(hipL),
1413
+ legLowerL: round(kneeL),
1414
+ legUpperR: round(hipR),
1415
+ legLowerR: round(kneeR),
1416
+ armUpperR: round(-10 - armSwing * swing),
1417
+ armLowerR: -16,
1418
+ armUpperL: round(10 + armSwing * swing),
1419
+ armLowerL: 16
1420
+ };
1421
+ }
1422
+ function gait(c, run) {
1423
+ const stride = (run ? 34 : 24) + (run ? 40 : 30) * c.e + c.jit(3);
1424
+ const lift = run ? 40 : 26;
1425
+ const armSwing = (run ? 26 : 16) + 20 * c.e;
1426
+ const halfDur = (run ? 0.26 : 0.42) + c.jit(0.02);
1427
+ const lean = run ? c.facing * -6 : 0;
1428
+ const steps = c.cycles * 2;
1429
+ const d = dur2(halfDur, c.sp);
1430
+ const intro = dur2(0.16, c.sp);
1431
+ const keys = [];
1432
+ for (let k = 0; k <= steps; k++) {
1433
+ const pose = { ...gaitPose(k / 2, stride, lift, armSwing, c.facing), chest: lean };
1434
+ keys.push(poseTo(c.g, pose, { duration: k === 0 ? intro : d, ease: k === 0 ? "easeOutQuad" : "linear" }));
1435
+ }
1436
+ const total = intro + steps * d;
1437
+ const travel = c.travel ?? stride * 2;
1438
+ const children = [seq(...keys)];
1439
+ if (travel !== 0) {
1440
+ children.push(tween(c.g, { x: c.at[0] + c.facing * travel * c.cycles }, { duration: total, ease: "linear", label: "travel" }));
1441
+ }
1442
+ return beat(run ? "run" : "walk", {}, [par(...children)]);
1443
+ }
1444
+ function jumpBeat(c) {
1445
+ const h = 120 + 150 * c.e;
1446
+ const [y0] = [c.at[1]];
1447
+ const CROUCH = { legUpperL: 18, legLowerL: 54, legUpperR: -18, legLowerR: 54, armUpperL: 28, armUpperR: -28 };
1448
+ const LAUNCH = { legUpperL: 0, legLowerL: 0, legUpperR: 0, legLowerR: 0, armUpperL: 150, armUpperR: -150 };
1449
+ const TUCK = { legUpperL: -28, legLowerL: 66, legUpperR: -28, legLowerR: 66, armUpperL: 124, armUpperR: -124 };
1450
+ const REST = { legUpperL: 3, legLowerL: -2, legUpperR: -3, legLowerR: 2, armUpperL: 10, armLowerL: 8, armUpperR: -10, armLowerR: -8 };
1451
+ const j = c.jit(0.03);
1452
+ return beat("jump", {}, [
1453
+ seq(
1454
+ par(poseTo(c.g, CROUCH, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" }), tween(c.g, { y: y0 + 26 }, { duration: dur2(0.24, c.sp), ease: "easeOutQuad" })),
1455
+ par(poseTo(c.g, LAUNCH, { duration: dur2(0.22 + j, c.sp), ease: "easeOutCubic" }), tween(c.g, { y: y0 - h }, { duration: dur2(0.36, c.sp), ease: "easeOutCubic", label: "launch" })),
1456
+ poseTo(c.g, TUCK, { duration: dur2(0.22, c.sp) }),
1457
+ par(poseTo(c.g, CROUCH, { duration: dur2(0.28, c.sp), ease: "easeInQuad" }), tween(c.g, { y: y0 + 18 }, { duration: dur2(0.3, c.sp), ease: "easeInCubic", label: "land" })),
1458
+ par(poseTo(c.g, REST, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }), tween(c.g, { y: y0 }, { duration: dur2(0.45, c.sp), ease: "easeOutBack" }))
1459
+ )
1460
+ ]);
1461
+ }
1462
+ function danceBeat(c) {
1463
+ const y0 = c.at[1];
1464
+ const sway = 8 + 6 * c.e;
1465
+ const armUp = 130 + 30 * c.e;
1466
+ const A = { chest: sway, head: -sway * 0.5, armUpperR: -armUp, armLowerR: -20, armUpperL: 40, armLowerL: 30, legUpperL: 8, legUpperR: -2 };
1467
+ const B = { chest: -sway, head: sway * 0.5, armUpperL: armUp, armLowerL: 20, armUpperR: -40, armLowerR: -30, legUpperL: 2, legUpperR: -8 };
1468
+ const d = dur2(0.34, c.sp);
1469
+ const keys = [];
1470
+ for (let k = 0; k < c.cycles * 2; k++) {
1471
+ const pose = k % 2 === 0 ? A : B;
1472
+ keys.push(par(
1473
+ poseTo(c.g, pose, { duration: d, ease: "easeInOutQuad" }),
1474
+ tween(c.g, { y: y0 - (k % 2 === 0 ? 14 : 0) }, { duration: d, ease: "easeInOutQuad" })
1475
+ ));
1476
+ }
1477
+ keys.push(tween(c.g, { y: y0 }, { duration: d }));
1478
+ return beat("dance", {}, [seq(...keys)]);
1479
+ }
1480
+ function waveBeat(c) {
1481
+ const n3 = 3 + Math.round(c.rand() * 2);
1482
+ const amp = 16 + 10 * c.e;
1483
+ const steps = [poseTo(c.g, { armUpperR: -150, armLowerR: -24 }, { duration: dur2(0.4, c.sp), ease: "easeOutBack" })];
1484
+ for (let k = 0; k < n3; k++) {
1485
+ steps.push(poseTo(c.g, { armLowerR: -24 + (k % 2 === 0 ? amp : -amp) }, { duration: dur2(0.22, c.sp), ease: "easeInOutQuad" }));
1486
+ }
1487
+ steps.push(poseTo(c.g, { armUpperR: -10, armLowerR: -8 }, { duration: dur2(0.4, c.sp), ease: "easeInOutCubic" }));
1488
+ return beat("wave", {}, [seq(...steps)]);
1489
+ }
1490
+ function cheerBeat(c) {
1491
+ const y0 = c.at[1];
1492
+ const UP = { armUpperL: 152, armLowerL: 8, armUpperR: -152, armLowerR: -8 };
1493
+ const d = dur2(0.3, c.sp);
1494
+ const keys = [poseTo(c.g, UP, { duration: dur2(0.35, c.sp), ease: "easeOutBack" })];
1495
+ for (let k = 0; k < c.cycles; k++) {
1496
+ keys.push(par(tween(c.g, { y: y0 - 28 }, { duration: d, ease: "easeOutQuad" }), poseTo(c.g, { armUpperL: 160, armUpperR: -160 }, { duration: d })));
1497
+ keys.push(par(tween(c.g, { y: y0 }, { duration: d, ease: "easeInQuad" }), poseTo(c.g, { armUpperL: 145, armUpperR: -145 }, { duration: d })));
1498
+ }
1499
+ return beat("cheer", {}, [seq(...keys)]);
1500
+ }
1501
+ function characterPreset(name, opts) {
1502
+ const c = ctx2(opts);
1503
+ let tl;
1504
+ switch (name) {
1505
+ case "walk":
1506
+ tl = gait(c, false);
1507
+ break;
1508
+ case "run":
1509
+ tl = gait(c, true);
1510
+ break;
1511
+ case "jump":
1512
+ tl = jumpBeat(c);
1513
+ break;
1514
+ case "dance":
1515
+ tl = danceBeat(c);
1516
+ break;
1517
+ case "wave":
1518
+ tl = waveBeat(c);
1519
+ break;
1520
+ case "cheer":
1521
+ tl = cheerBeat(c);
1522
+ break;
1523
+ default: {
1524
+ const _exhaustive = name;
1525
+ throw new Error(`unknown characterPreset "${_exhaustive}"`);
1526
+ }
1527
+ }
1528
+ return c.label && tl.kind === "beat" ? { ...tl, name: c.label } : tl;
1529
+ }
1530
+
1531
+ // ../core/src/figure.ts
1532
+ var K2 = 0.5523;
1533
+ var n2 = (v) => Number(v.toFixed(2));
1534
+ function limb(a, b, y0, y1) {
1535
+ const ka = n2(a * K2), kb = n2(b * K2);
1536
+ return `M ${-a} ${y0} C ${-a} ${n2(y0 - ka)} ${-ka} ${n2(y0 - a)} 0 ${n2(y0 - a)} C ${ka} ${n2(y0 - a)} ${a} ${n2(y0 - ka)} ${a} ${y0} L ${b} ${y1} C ${b} ${n2(y1 + kb)} ${kb} ${n2(y1 + b)} 0 ${n2(y1 + b)} C ${-kb} ${n2(y1 + b)} ${-b} ${n2(y1 + kb)} ${-b} ${y1} Z`;
1537
+ }
1538
+ function rrect(a, b, y0, y1, r) {
1539
+ return `M ${n2(-a + r)} ${y0} L ${n2(a - r)} ${y0} Q ${a} ${y0} ${a} ${n2(y0 + r)} L ${b} ${n2(y1 - r)} Q ${b} ${y1} ${n2(b - r)} ${y1} L ${n2(-b + r)} ${y1} Q ${-b} ${y1} ${-b} ${n2(y1 - r)} L ${-a} ${n2(y0 + r)} Q ${-a} ${y0} ${n2(-a + r)} ${y0} Z`;
1540
+ }
1541
+ function darken(hex, f) {
1542
+ const h = hex.replace("#", "");
1543
+ const v = parseInt(h.length === 3 ? [...h].map((c) => c + c).join("") : h, 16);
1544
+ const ch = (s) => Math.max(0, Math.min(255, Math.round((v >> s & 255) * (1 - f))));
1545
+ const hx = (x) => x.toString(16).padStart(2, "0");
1546
+ return `#${hx(ch(16))}${hx(ch(8))}${hx(ch(0))}`;
1547
+ }
1548
+ var DEF = {
1549
+ clean: { skin: "#E9B58E", hair: "#2B313F", top: "#E86C4A", pants: "#39425C", shoe: "#20242F", accent: "#E86C4A" },
1550
+ cute: { skin: "#FFD2A6", hair: "#5B4636", top: "#FF7E5F", pants: "#3E6F8E", shoe: "#272B38", accent: "#FF7E5F" }
1551
+ };
1552
+ function resolvePal(style, p = {}) {
1553
+ const d = DEF[style];
1554
+ const accent = p.accent ?? d.accent;
1555
+ const top = p.top ?? (style === "clean" ? accent : d.top);
1556
+ const skin = p.skin ?? d.skin;
1557
+ const hair = p.hair ?? d.hair;
1558
+ const pants = p.pants ?? d.pants;
1559
+ const shoe = p.shoe ?? d.shoe;
1560
+ return {
1561
+ skin,
1562
+ skinSh: darken(skin, 0.12),
1563
+ hair,
1564
+ hairSh: darken(hair, 0.14),
1565
+ top,
1566
+ topSh: darken(top, 0.12),
1567
+ pants,
1568
+ pantsSh: darken(pants, 0.14),
1569
+ shoe,
1570
+ shoeSh: darken(shoe, 0.22),
1571
+ eye: "#2B313F",
1572
+ cheek: "#FF9E7E",
1573
+ white: "#FFFFFF",
1574
+ mouth: "#8A4233"
1575
+ };
1576
+ }
1577
+ var fp = (id, d, fill, stroke, sw = 0, opacity = 1) => path({ id, d, x: 0, y: 0, fill, opacity, ...stroke && sw > 0 ? { stroke, strokeWidth: sw } : {} });
1578
+ function cleanParts(p, face) {
1579
+ const HC = -42;
1580
+ return {
1581
+ upperArm: (j) => [fp(`${j}-sleeve`, limb(12, 10, 2, 58), p.top)],
1582
+ forearm: (j) => [
1583
+ fp(`${j}-elbow`, ovalPath(10, 10, 0, 3), p.skin),
1584
+ fp(`${j}-fore`, limb(10, 8, 2, 48), p.skin),
1585
+ fp(`${j}-hand`, ovalPath(11, 12, 0, 50), p.skin)
1586
+ ],
1587
+ thigh: (j) => [fp(`${j}-thigh`, limb(15, 13, 2, 72), p.pants)],
1588
+ shin: (j) => [
1589
+ fp(`${j}-knee`, ovalPath(13, 13, 0, 2), p.pants),
1590
+ fp(`${j}-shin`, limb(13, 11, 2, 62), p.pants),
1591
+ fp(`${j}-shoe`, ovalPath(15, 9, 4, 67), p.shoe)
1592
+ ],
1593
+ torso: (j) => [
1594
+ fp(`${j}-shadow`, rrect(38, 26, -28, 52, 20), p.topSh),
1595
+ fp(`${j}-top`, rrect(40, 27, -30, 52, 22), p.top),
1596
+ fp(`${j}-pelvis`, rrect(29, 24, 46, 104, 14), p.pants)
1597
+ ],
1598
+ head: (j) => [
1599
+ fp(`${j}-neck`, rrect(9, 9, 2, 22, 5), p.skin),
1600
+ fp(`${j}-skin`, ovalPath(42, 46, 0, HC), p.skin),
1601
+ fp(`${j}-hair`, ovalPath(44, 27, 0, HC - 31), p.hair),
1602
+ fp(`${j}-hairL`, ovalPath(8, 14, -39, HC - 18), p.hair),
1603
+ fp(`${j}-hairR`, ovalPath(8, 14, 39, HC - 18), p.hair),
1604
+ ...face ? [fp(`${j}-eyeL`, ovalPath(5, 7, -14, HC + 2), p.eye), fp(`${j}-eyeR`, ovalPath(5, 7, 14, HC + 2), p.eye)] : []
1605
+ ]
1606
+ };
1607
+ }
1608
+ var CUTE_HAIR = "M -64 -54 C -78 -96 -50 -126 0 -126 C 50 -126 78 -96 64 -54 C 60 -34 48 -28 41 -33 C 35 -54 23 -60 9 -60 C 3 -60 -3 -60 -9 -60 C -23 -60 -35 -54 -41 -33 C -48 -28 -60 -34 -64 -54 Z";
1609
+ function cuteParts(p, face) {
1610
+ const HC = -50;
1611
+ return {
1612
+ upperArm: (j) => [fp(`${j}-sleeve`, limb(15, 13, 2, 58), p.top, p.topSh, 2.5)],
1613
+ forearm: (j) => [
1614
+ fp(`${j}-elbow`, ovalPath(12, 12, 0, 3), p.skin, p.skinSh, 2.5),
1615
+ fp(`${j}-fore`, limb(12, 10, 2, 46), p.skin, p.skinSh, 2.5),
1616
+ fp(`${j}-hand`, ovalPath(13, 14, 0, 50), p.skin, p.skinSh, 2.5)
1617
+ ],
1618
+ thigh: (j) => [fp(`${j}-thigh`, limb(19, 16, 2, 72), p.pants, p.pantsSh, 2.5)],
1619
+ shin: (j) => [
1620
+ fp(`${j}-knee`, ovalPath(16, 16, 0, 2), p.pants, p.pantsSh, 2.5),
1621
+ fp(`${j}-shin`, limb(15, 12, 2, 60), p.pants, p.pantsSh, 2.5),
1622
+ fp(`${j}-shoe`, ovalPath(18, 11, 5, 66), p.shoe, darken(p.shoe, 0.25), 2.5)
1623
+ ],
1624
+ torso: (j) => [
1625
+ fp(`${j}-shadow`, rrect(40, 34, -18, 52, 16), p.topSh),
1626
+ fp(`${j}-top`, rrect(42, 35, -20, 52, 18), p.top, p.topSh, 2.5),
1627
+ fp(`${j}-pelvis`, rrect(36, 30, 46, 104, 16), p.pants, p.pantsSh, 2.5)
1628
+ ],
1629
+ head: (j) => [
1630
+ fp(`${j}-neck`, ovalPath(15, 12, 0, 12), p.skinSh),
1631
+ fp(`${j}-skin`, ovalPath(42, 46, 0, HC + 4), p.skin, p.skinSh, 2.5),
1632
+ fp(`${j}-cheekL`, ovalPath(11, 8, -40, HC + 22), p.cheek, void 0, 0, 0.5),
1633
+ fp(`${j}-cheekR`, ovalPath(11, 8, 40, HC + 22), p.cheek, void 0, 0, 0.5),
1634
+ fp(`${j}-hair`, CUTE_HAIR, p.hair, p.hairSh, 2),
1635
+ ...face ? [
1636
+ fp(`${j}-eyeL`, ovalPath(10, 13, -25, HC + 8), p.eye),
1637
+ fp(`${j}-eyeR`, ovalPath(10, 13, 25, HC + 8), p.eye),
1638
+ fp(`${j}-glL`, ovalPath(3.4, 3.4, -28, HC + 3), p.white),
1639
+ fp(`${j}-glR`, ovalPath(3.4, 3.4, 22, HC + 3), p.white),
1640
+ path({ id: `${j}-mouth`, d: "M -15 0 Q 0 15 15 0", x: 0, y: HC + 28, fill: "none", stroke: p.mouth, strokeWidth: 5 })
1641
+ ] : []
1642
+ ]
1643
+ };
1644
+ }
1645
+ function buildSkeleton(id, S) {
1646
+ const arm = (side, x, r1, r2) => ({
1647
+ name: `armUpper${side}`,
1648
+ at: [x, -14],
1649
+ length: 60,
1650
+ width: 0,
1651
+ rotation: r1,
1652
+ shape: S.upperArm(`${id}-armUpper${side}`),
1653
+ children: [{ name: `armLower${side}`, at: [0, 60], length: 56, width: 0, rotation: r2, shape: S.forearm(`${id}-armLower${side}`) }]
1654
+ });
1655
+ const leg = (side, x, r1, r2) => ({
1656
+ name: `legUpper${side}`,
1657
+ at: [x, 76],
1658
+ length: 76,
1659
+ width: 0,
1660
+ rotation: r1,
1661
+ shape: S.thigh(`${id}-legUpper${side}`),
1662
+ children: [{ name: `legLower${side}`, at: [0, 76], length: 72, width: 0, rotation: r2, shape: S.shin(`${id}-legLower${side}`) }]
1663
+ });
1664
+ return {
1665
+ name: "chest",
1666
+ at: [0, 0],
1667
+ shape: S.torso(`${id}-chest`),
1668
+ children: [
1669
+ { name: "head", at: [0, -42], rotation: 0, shape: S.head(`${id}-head`) },
1670
+ arm("L", -40, 8, 6),
1671
+ arm("R", 40, -8, -6),
1672
+ leg("L", -20, 3, -2),
1673
+ leg("R", 20, -3, 2)
1674
+ ]
1675
+ };
1676
+ }
1677
+ function figure(opts = {}) {
1678
+ const style = opts.style ?? "clean";
1679
+ const pal = resolvePal(style, opts.palette);
1680
+ const face = opts.face ?? true;
1681
+ const id = opts.id ?? "figure";
1682
+ const parts = style === "clean" ? cleanParts(pal, face) : cuteParts(pal, face);
1683
+ const rigOpts = { id };
1684
+ if (opts.x !== void 0) rigOpts.x = opts.x;
1685
+ if (opts.y !== void 0) rigOpts.y = opts.y;
1686
+ if (opts.scale !== void 0) rigOpts.scale = opts.scale;
1687
+ if (opts.opacity !== void 0) rigOpts.opacity = opts.opacity;
1688
+ return rig(buildSkeleton(id, parts), rigOpts);
1689
+ }
1690
+
1691
+ // ../core/src/motionOps.ts
1692
+ var MOTION_OPS = ["rotate", "zoom", "ken-burns", "slide-in", "fade", "draw-on", "pulse"];
1693
+ var clamp013 = (n3) => Math.max(0, Math.min(1, n3));
1694
+ function settleEase2(e) {
1695
+ return e < 0.34 ? "easeOutCubic" : e < 0.67 ? "easeOutBack" : "easeOutElastic";
1696
+ }
1697
+ function fromVec2(from, dist) {
1698
+ switch (from) {
1699
+ case "right":
1700
+ return [dist, 0];
1701
+ case "top":
1702
+ return [0, -dist];
1703
+ case "bottom":
1704
+ return [0, dist];
1705
+ default:
1706
+ return [-dist, 0];
1707
+ }
1708
+ }
1709
+ var motionOpLabel = (name, target) => `op-${name}-${target}`;
1710
+ function motionOp(name, target, opts = {}) {
1711
+ const e = clamp013(opts.energy ?? 0.5);
1712
+ const sp = Math.max(0.25, opts.speed ?? 1);
1713
+ const amt = opts.amount ?? 1;
1714
+ const b = { scale: 1, x: 0, y: 0, rotation: 0, ...opts.base };
1715
+ const d = (base) => base / sp;
1716
+ const label = motionOpLabel(name, target);
1717
+ switch (name) {
1718
+ case "rotate":
1719
+ return { timeline: beat(label, {}, [tween(target, { rotation: b.rotation + 360 * amt }, { duration: d(1), ease: settleEase2(e) })]) };
1720
+ case "zoom": {
1721
+ const peak = b.scale * (1 + 0.22 * amt);
1722
+ return {
1723
+ timeline: beat(label, {}, [
1724
+ seq(
1725
+ tween(target, { scale: peak }, { duration: d(0.4), ease: "easeOutBack" }),
1726
+ tween(target, { scale: b.scale }, { duration: d(0.45), ease: "easeInOutQuad" })
1727
+ )
1728
+ ])
1729
+ };
1730
+ }
1731
+ case "ken-burns":
1732
+ return {
1733
+ timeline: beat(label, {}, [
1734
+ par(
1735
+ tween(target, { scale: b.scale * (1 + 0.1 * amt) }, { duration: d(3), ease: "easeInOutQuad" }),
1736
+ tween(target, { x: b.x + 26 * amt, y: b.y - 16 * amt }, { duration: d(3), ease: "easeInOutQuad" })
1737
+ )
1738
+ ])
1739
+ };
1740
+ case "slide-in": {
1741
+ const [dx, dy] = fromVec2(opts.from ?? "left", 320 * amt);
1742
+ return {
1743
+ setup: { [target]: { x: b.x + dx, y: b.y + dy, opacity: 0 } },
1744
+ timeline: beat(label, {}, [
1745
+ par(
1746
+ tween(target, { x: b.x, y: b.y }, { duration: d(0.7), ease: settleEase2(e) }),
1747
+ tween(target, { opacity: 1 }, { duration: d(0.4), ease: "easeOutQuad" })
1748
+ )
1749
+ ])
1750
+ };
1751
+ }
1752
+ case "fade":
1753
+ return {
1754
+ setup: { [target]: { opacity: 0 } },
1755
+ timeline: beat(label, {}, [tween(target, { opacity: 1 }, { duration: d(0.6), ease: "easeOutQuad" })])
1756
+ };
1757
+ case "draw-on":
1758
+ return {
1759
+ setup: { [target]: { progress: 0 } },
1760
+ timeline: beat(label, {}, [tween(target, { progress: 1 }, { duration: d(1.3), ease: "easeInOutQuad" })])
1761
+ };
1762
+ case "pulse": {
1763
+ const hi = b.scale * (1 + 0.12 * amt);
1764
+ const pulses = 2 + Math.round(amt);
1765
+ const steps = [];
1766
+ for (let i = 0; i < pulses; i++) {
1767
+ steps.push(tween(target, { scale: hi }, { duration: d(0.22), ease: "easeOutQuad" }));
1768
+ steps.push(tween(target, { scale: b.scale }, { duration: d(0.22), ease: "easeInQuad" }));
1769
+ }
1770
+ return { timeline: beat(label, {}, [seq(...steps)]) };
1771
+ }
1772
+ }
1773
+ }
1774
+
887
1775
  // ../core/src/audio.ts
888
1776
  var SFX_DURATION = {
889
1777
  whoosh: 0.35,
@@ -929,6 +1817,15 @@ function resolveAudioPlan(compiled) {
929
1817
  });
930
1818
  }
931
1819
  cues.sort((a, b) => a.t - b.t);
1820
+ return {
1821
+ duration,
1822
+ bgm: resolveBgm(audio.bgm),
1823
+ cues,
1824
+ duckWindows: mergeDuckWindows(cues, duration),
1825
+ warnings
1826
+ };
1827
+ }
1828
+ function mergeDuckWindows(cues, duration) {
932
1829
  const duckWindows = [];
933
1830
  for (const cue of cues) {
934
1831
  const window = { t0: cue.t, t1: Math.min(duration, cue.t + cue.duration) };
@@ -936,23 +1833,68 @@ function resolveAudioPlan(compiled) {
936
1833
  if (last && window.t0 <= last.t1 + 0.1) last.t1 = Math.max(last.t1, window.t1);
937
1834
  else duckWindows.push(window);
938
1835
  }
939
- let bgm = null;
940
- if (audio.bgm) {
941
- const b = audio.bgm;
942
- const duck = b.duck === false ? null : {
943
- depth: b.duck?.depth ?? 0.5,
944
- attack: b.duck?.attack ?? 0.05,
945
- release: b.duck?.release ?? 0.25
946
- };
947
- bgm = {
948
- source: b.file ? { kind: "file", path: b.file } : { kind: "synth", name: b.synth ?? "ambient-pad" },
949
- gain: b.gain ?? 0.5,
950
- fadeIn: b.fadeIn ?? 0,
951
- fadeOut: b.fadeOut ?? 0,
952
- duck
953
- };
1836
+ return duckWindows;
1837
+ }
1838
+ function resolveBgm(b) {
1839
+ if (!b) return null;
1840
+ const duck = b.duck === false ? null : {
1841
+ depth: b.duck?.depth ?? 0.5,
1842
+ attack: b.duck?.attack ?? 0.05,
1843
+ release: b.duck?.release ?? 0.25
1844
+ };
1845
+ return {
1846
+ source: b.file ? { kind: "file", path: b.file } : { kind: "synth", name: b.synth ?? "ambient-pad" },
1847
+ gain: b.gain ?? 0.5,
1848
+ fadeIn: b.fadeIn ?? 0,
1849
+ fadeOut: b.fadeOut ?? 0,
1850
+ duck
1851
+ };
1852
+ }
1853
+ function resolveCompositionAudioPlan(comp) {
1854
+ const audio = comp.ir.audio;
1855
+ const duration = comp.duration;
1856
+ const warnings = [];
1857
+ const cues = [];
1858
+ for (const placement of comp.scenes) {
1859
+ const plan = resolveAudioPlan(placement.compiled);
1860
+ if (!plan) continue;
1861
+ if (plan.bgm) {
1862
+ warnings.push(`scene "${placement.id}": per-scene bgm ignored \u2014 set bgm at the composition level`);
1863
+ }
1864
+ for (const w of plan.warnings) warnings.push(`scene "${placement.id}": ${w}`);
1865
+ for (const cue of plan.cues) {
1866
+ const t = cue.t + placement.start;
1867
+ if (t >= duration) continue;
1868
+ cues.push({ ...cue, t });
1869
+ }
954
1870
  }
955
- return { duration, bgm, cues, duckWindows, warnings };
1871
+ for (const [index, cue] of (audio?.cues ?? []).entries()) {
1872
+ if (typeof cue.at !== "number") {
1873
+ warnings.push(`composition cue[${index}]: "at" must be an absolute number (no composition labels) \u2014 dropped`);
1874
+ continue;
1875
+ }
1876
+ const t = Math.max(0, cue.at + (cue.offset ?? 0));
1877
+ const cueDuration = cue.sfx ? SFX_DURATION[cue.sfx] : FILE_CUE_DURATION;
1878
+ if (t >= duration) {
1879
+ warnings.push(`composition cue[${index}] at ${t.toFixed(2)}s past the composition end \u2014 dropped`);
1880
+ continue;
1881
+ }
1882
+ cues.push({
1883
+ t,
1884
+ gain: cue.gain ?? 1,
1885
+ duration: cueDuration,
1886
+ source: cue.sfx ? { kind: "sfx", name: cue.sfx, params: cue.params ?? {} } : { kind: "file", path: cue.file }
1887
+ });
1888
+ }
1889
+ if (!audio?.bgm && cues.length === 0) return null;
1890
+ cues.sort((a, b) => a.t - b.t);
1891
+ return {
1892
+ duration,
1893
+ bgm: resolveBgm(audio?.bgm),
1894
+ cues,
1895
+ duckWindows: mergeDuckWindows(cues, duration),
1896
+ warnings
1897
+ };
956
1898
  }
957
1899
 
958
1900
  // ../core/src/behaviors.ts
@@ -976,8 +1918,8 @@ function valueNoise(x, seed) {
976
1918
  const b = hash01(i + 1, seed) * 2 - 1;
977
1919
  return a + (b - a) * u;
978
1920
  }
979
- function hash01(n, seed) {
980
- let h = n * 374761393 + seed * 668265263 | 0;
1921
+ function hash01(n3, seed) {
1922
+ let h = n3 * 374761393 + seed * 668265263 | 0;
981
1923
  h = h ^ h >>> 13 | 0;
982
1924
  h = Math.imul(h, 1274126177);
983
1925
  h = (h ^ h >>> 16) >>> 0;
@@ -1070,8 +2012,8 @@ function isColor(v) {
1070
2012
  function parseColor(hex) {
1071
2013
  let h = hex.slice(1);
1072
2014
  if (h.length <= 4) h = [...h].map((c) => c + c).join("");
1073
- const n = parseInt(h.padEnd(8, "f"), 16);
1074
- return [n >>> 24 & 255, n >>> 16 & 255, n >>> 8 & 255, n & 255];
2015
+ const n3 = parseInt(h.padEnd(8, "f"), 16);
2016
+ return [n3 >>> 24 & 255, n3 >>> 16 & 255, n3 >>> 8 & 255, n3 & 255];
1075
2017
  }
1076
2018
  function formatColor([r, g, b, a]) {
1077
2019
  const hex = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0");
@@ -1091,26 +2033,86 @@ function lerpValue(from, to2, u) {
1091
2033
  a[3] + (b[3] - a[3]) * u
1092
2034
  ]);
1093
2035
  }
2036
+ if (looksLikePath(from) && looksLikePath(to2)) {
2037
+ const a = tokenizePath(from);
2038
+ const b = tokenizePath(to2);
2039
+ if (a && b && morphCompatible(a, b)) return morphPath(a, b, u);
2040
+ return u < 0.5 ? from : to2;
2041
+ }
1094
2042
  return to2;
1095
2043
  }
2044
+ var PATH_BODY = /^[\sMmLlHhVvCcSsQqTtAaZz0-9.,eE+-]+$/;
2045
+ function looksLikePath(v) {
2046
+ return typeof v === "string" && /^\s*[Mm]/.test(v) && PATH_BODY.test(v);
2047
+ }
2048
+ var PATH_TOKEN = /([MmLlHhVvCcSsQqTtAaZz])|(-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?)/g;
2049
+ function tokenizePath(d) {
2050
+ const out = [];
2051
+ let cur = null;
2052
+ let m;
2053
+ PATH_TOKEN.lastIndex = 0;
2054
+ while (m = PATH_TOKEN.exec(d)) {
2055
+ if (m[1]) out.push(cur = { cmd: m[1], nums: [] });
2056
+ else if (m[2]) {
2057
+ if (!cur) return null;
2058
+ cur.nums.push(parseFloat(m[2]));
2059
+ }
2060
+ }
2061
+ return out.length ? out : null;
2062
+ }
2063
+ function morphCompatible(a, b) {
2064
+ if (a.length !== b.length) return false;
2065
+ for (let i = 0; i < a.length; i++) {
2066
+ const ca = a[i];
2067
+ const cb = b[i];
2068
+ if (ca.cmd !== cb.cmd || ca.nums.length !== cb.nums.length) return false;
2069
+ if (ca.cmd === "A" || ca.cmd === "a") return false;
2070
+ }
2071
+ return true;
2072
+ }
2073
+ var fmtNum = (v) => {
2074
+ const r = Number(v.toFixed(3));
2075
+ return Object.is(r, -0) ? "0" : String(r);
2076
+ };
2077
+ function morphPath(a, b, u) {
2078
+ let s = "";
2079
+ for (let i = 0; i < a.length; i++) {
2080
+ const an = a[i].nums;
2081
+ const bn = b[i].nums;
2082
+ s += (i ? " " : "") + a[i].cmd;
2083
+ for (let j = 0; j < an.length; j++) s += " " + fmtNum(an[j] + (bn[j] - an[j]) * u);
2084
+ }
2085
+ return s;
2086
+ }
1096
2087
 
1097
2088
  // ../core/src/evaluate.ts
1098
2089
  var IDENTITY = [1, 0, 0, 1, 0, 0];
1099
- function multiply(m, n) {
2090
+ function multiply(m, n3) {
1100
2091
  return [
1101
- m[0] * n[0] + m[2] * n[1],
1102
- m[1] * n[0] + m[3] * n[1],
1103
- m[0] * n[2] + m[2] * n[3],
1104
- m[1] * n[2] + m[3] * n[3],
1105
- m[0] * n[4] + m[2] * n[5] + m[4],
1106
- m[1] * n[4] + m[3] * n[5] + m[5]
2092
+ m[0] * n3[0] + m[2] * n3[1],
2093
+ m[1] * n3[0] + m[3] * n3[1],
2094
+ m[0] * n3[2] + m[2] * n3[3],
2095
+ m[1] * n3[2] + m[3] * n3[3],
2096
+ m[0] * n3[4] + m[2] * n3[5] + m[4],
2097
+ m[1] * n3[4] + m[3] * n3[5] + m[5]
1107
2098
  ];
1108
2099
  }
1109
- function localMatrix(x, y, rotationDeg, scale) {
2100
+ function localMatrix(x, y, rotationDeg, scale, scaleX = 1, scaleY = 1, skewXDeg = 0, skewYDeg = 0) {
1110
2101
  const r = rotationDeg * Math.PI / 180;
1111
- const cos = Math.cos(r) * scale;
1112
- const sin = Math.sin(r) * scale;
1113
- return [cos, sin, -sin, cos, x, y];
2102
+ if (scaleX === 1 && scaleY === 1 && skewXDeg === 0 && skewYDeg === 0) {
2103
+ const cos = Math.cos(r) * scale;
2104
+ const sin = Math.sin(r) * scale;
2105
+ return [cos, sin, -sin, cos, x, y];
2106
+ }
2107
+ const c = Math.cos(r);
2108
+ const s = Math.sin(r);
2109
+ const tx = Math.tan(skewXDeg * Math.PI / 180);
2110
+ const ty = Math.tan(skewYDeg * Math.PI / 180);
2111
+ const R = [c, s, -s, c, 0, 0];
2112
+ const K3 = [1, ty, tx, 1, 0, 0];
2113
+ const S = [scale * scaleX, 0, 0, scale * scaleY, 0, 0];
2114
+ const m = multiply(R, multiply(K3, S));
2115
+ return [m[0], m[1], m[2], m[3], x, y];
1114
2116
  }
1115
2117
  var ANCHOR_FACTORS = {
1116
2118
  "top-left": [0, 0],
@@ -1135,53 +2137,86 @@ function behaviorEnvelope(b, t) {
1135
2137
  if (Number.isFinite(until) && ramp > 0) envelope = Math.min(envelope, (until - t) / ramp);
1136
2138
  return Math.max(0, Math.min(1, envelope));
1137
2139
  }
1138
- function evaluate(compiled, t) {
1139
- const ops = [];
1140
- const valueAt = (target, prop, fallback) => {
1141
- let value = compiled.initialValues.get(`${target}.${prop}`) ?? fallback;
1142
- let segStart = Number.NEGATIVE_INFINITY;
1143
- const segs = compiled.segments.get(`${target}.${prop}`);
1144
- if (segs) {
2140
+ function sampleProp(compiled, t, target, prop, fallback) {
2141
+ let value = compiled.initialValues.get(`${target}.${prop}`) ?? fallback;
2142
+ let segStart = Number.NEGATIVE_INFINITY;
2143
+ const segs = compiled.segments.get(`${target}.${prop}`);
2144
+ if (segs) {
2145
+ let active;
2146
+ for (const seg of segs) {
2147
+ if (seg.t0 <= t) active = seg;
2148
+ else break;
2149
+ }
2150
+ if (active) {
2151
+ segStart = active.t0;
2152
+ if (t >= active.t1) {
2153
+ value = active.to;
2154
+ } else {
2155
+ const u = resolveEase(active.ease)((t - active.t0) / (active.t1 - active.t0));
2156
+ value = lerpValue(active.from, active.to, u);
2157
+ }
2158
+ }
2159
+ }
2160
+ if (prop === "x" || prop === "y" || prop === "rotation") {
2161
+ const drivers = compiled.motionPaths.get(target);
2162
+ if (drivers) {
1145
2163
  let active;
1146
- for (const seg of segs) {
1147
- if (seg.t0 <= t) active = seg;
2164
+ for (const d of drivers) {
2165
+ if (d.t0 <= t) active = d;
1148
2166
  else break;
1149
2167
  }
1150
- if (active) {
1151
- segStart = active.t0;
1152
- if (t >= active.t1) {
1153
- value = active.to;
1154
- } else {
1155
- const u = resolveEase(active.ease)((t - active.t0) / (active.t1 - active.t0));
1156
- value = lerpValue(active.from, active.to, u);
1157
- }
2168
+ if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
2169
+ const span = active.t1 - active.t0;
2170
+ const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
2171
+ if (prop === "x") value = pathPoint(active.points, active.closed, u, active.curviness)[0];
2172
+ else if (prop === "y") value = pathPoint(active.points, active.closed, u, active.curviness)[1];
2173
+ else value = pathTangentAngle(active.points, active.closed, u, active.curviness) + active.rotateOffset;
1158
2174
  }
1159
2175
  }
1160
- if (prop === "x" || prop === "y" || prop === "rotation") {
1161
- const drivers = compiled.motionPaths.get(target);
1162
- if (drivers) {
1163
- let active;
1164
- for (const d of drivers) {
1165
- if (d.t0 <= t) active = d;
1166
- else break;
1167
- }
1168
- if (active && active.t0 >= segStart && (prop !== "rotation" || active.autoRotate) && active.points.length > 0) {
1169
- const span = active.t1 - active.t0;
1170
- const u = span <= 0 ? 1 : resolveEase(active.ease)(Math.max(0, Math.min(1, (t - active.t0) / span)));
1171
- if (prop === "x") value = pathPoint(active.points, active.closed, u)[0];
1172
- else if (prop === "y") value = pathPoint(active.points, active.closed, u)[1];
1173
- else value = pathTangentAngle(active.points, active.closed, u) + active.rotateOffset;
1174
- }
1175
- }
2176
+ }
2177
+ for (const b of compiled.ir.behaviors ?? []) {
2178
+ if (b.target === target && b.prop === prop && typeof value === "number") {
2179
+ const envelope = behaviorEnvelope(b, t);
2180
+ if (envelope > 0) value = value + envelope * sampleBehavior(b.behavior, t);
1176
2181
  }
1177
- for (const b of compiled.ir.behaviors ?? []) {
1178
- if (b.target === target && b.prop === prop && typeof value === "number") {
1179
- const envelope = behaviorEnvelope(b, t);
1180
- if (envelope > 0) value = value + envelope * sampleBehavior(b.behavior, t);
1181
- }
2182
+ }
2183
+ return value;
2184
+ }
2185
+ function nodeParentMatrix(compiled, id, t) {
2186
+ const num = (target, prop, fallback) => {
2187
+ const v = sampleProp(compiled, t, target, prop, fallback);
2188
+ return typeof v === "number" ? v : fallback;
2189
+ };
2190
+ let result = null;
2191
+ const walk = (node, parent) => {
2192
+ if (node.id === id) {
2193
+ result = parent;
2194
+ return true;
1182
2195
  }
1183
- return value;
2196
+ if (node.type === "group") {
2197
+ const m = multiply(
2198
+ parent,
2199
+ localMatrix(
2200
+ num(node.id, "x", node.props.x),
2201
+ num(node.id, "y", node.props.y),
2202
+ num(node.id, "rotation", node.props.rotation ?? 0),
2203
+ num(node.id, "scale", node.props.scale ?? 1),
2204
+ num(node.id, "scaleX", node.props.scaleX ?? 1),
2205
+ num(node.id, "scaleY", node.props.scaleY ?? 1),
2206
+ num(node.id, "skewX", node.props.skewX ?? 0),
2207
+ num(node.id, "skewY", node.props.skewY ?? 0)
2208
+ )
2209
+ );
2210
+ for (const child of node.children) if (walk(child, m)) return true;
2211
+ }
2212
+ return false;
1184
2213
  };
2214
+ for (const node of compiled.ir.nodes) if (walk(node, IDENTITY)) break;
2215
+ return result;
2216
+ }
2217
+ function evaluate(compiled, t) {
2218
+ const ops = [];
2219
+ const valueAt = (target, prop, fallback) => sampleProp(compiled, t, target, prop, fallback);
1185
2220
  const num = (target, prop, fallback) => {
1186
2221
  const v = valueAt(target, prop, fallback);
1187
2222
  return typeof v === "number" ? v : fallback;
@@ -1194,8 +2229,9 @@ function evaluate(compiled, t) {
1194
2229
  const v = valueAt(target, prop, base ?? "");
1195
2230
  return v === "" && base === void 0 ? void 0 : String(v);
1196
2231
  };
1197
- const walk = (node, parent, parentOpacity) => {
2232
+ const walk = (node, parent, parentOpacity, clips) => {
1198
2233
  const id = node.id;
2234
+ const clipSpread = clips.length > 0 ? { clips } : void 0;
1199
2235
  if (node.type === "line") {
1200
2236
  const opacity2 = parentOpacity * num(id, "opacity", node.props.opacity ?? 1);
1201
2237
  if (opacity2 <= 0) return;
@@ -1212,7 +2248,8 @@ function evaluate(compiled, t) {
1212
2248
  x2: x1 + (num(id, "x2", node.props.x2) - x1) * progress,
1213
2249
  y2: y1 + (num(id, "y2", node.props.y2) - y1) * progress,
1214
2250
  stroke: str(id, "stroke", node.props.stroke),
1215
- strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1)
2251
+ strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1),
2252
+ ...clipSpread
1216
2253
  });
1217
2254
  return;
1218
2255
  }
@@ -1224,13 +2261,19 @@ function evaluate(compiled, t) {
1224
2261
  num(id, "x", node.props.x),
1225
2262
  num(id, "y", node.props.y),
1226
2263
  num(id, "rotation", node.props.rotation ?? 0),
1227
- num(id, "scale", node.props.scale ?? 1)
2264
+ num(id, "scale", node.props.scale ?? 1),
2265
+ num(id, "scaleX", node.props.scaleX ?? 1),
2266
+ num(id, "scaleY", node.props.scaleY ?? 1),
2267
+ num(id, "skewX", node.props.skewX ?? 0),
2268
+ num(id, "skewY", node.props.skewY ?? 0)
1228
2269
  )
1229
2270
  );
1230
2271
  switch (node.type) {
1231
- case "group":
1232
- for (const child of node.children) walk(child, matrix, opacity);
2272
+ case "group": {
2273
+ const childClips = node.props.clip ? [...clips, { transform: matrix, shape: node.props.clip }] : clips;
2274
+ for (const child of node.children) walk(child, matrix, opacity, childClips);
1233
2275
  return;
2276
+ }
1234
2277
  case "rect":
1235
2278
  case "ellipse": {
1236
2279
  const width = num(id, "width", node.props.width);
@@ -1250,7 +2293,8 @@ function evaluate(compiled, t) {
1250
2293
  offsetY: -height * ay,
1251
2294
  ...fill !== void 0 && { fill },
1252
2295
  ...stroke !== void 0 && { stroke, strokeWidth },
1253
- ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) }
2296
+ ...node.type === "rect" && { radius: num(id, "radius", node.props.radius ?? 0) },
2297
+ ...clipSpread
1254
2298
  });
1255
2299
  return;
1256
2300
  }
@@ -1267,7 +2311,8 @@ function evaluate(compiled, t) {
1267
2311
  width,
1268
2312
  height,
1269
2313
  offsetX: -width * ax,
1270
- offsetY: -height * ay
2314
+ offsetY: -height * ay,
2315
+ ...clipSpread
1271
2316
  });
1272
2317
  return;
1273
2318
  }
@@ -1284,7 +2329,8 @@ function evaluate(compiled, t) {
1284
2329
  d: str(id, "d", node.props.d),
1285
2330
  progress: Math.max(0, Math.min(1, num(id, "progress", node.props.progress ?? 1))),
1286
2331
  ...fill !== void 0 && { fill },
1287
- ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) }
2332
+ ...stroke !== void 0 && { stroke, strokeWidth: num(id, "strokeWidth", node.props.strokeWidth ?? 1) },
2333
+ ...clipSpread
1288
2334
  });
1289
2335
  return;
1290
2336
  }
@@ -1307,13 +2353,14 @@ function evaluate(compiled, t) {
1307
2353
  fill: str(id, "fill", node.props.fill ?? "#ffffff"),
1308
2354
  letterSpacing: num(id, "letterSpacing", node.props.letterSpacing ?? 0),
1309
2355
  align: TEXT_ALIGN[ax] ?? "left",
1310
- baseline: TEXT_BASELINE[ay] ?? "top"
2356
+ baseline: TEXT_BASELINE[ay] ?? "top",
2357
+ ...clipSpread
1311
2358
  });
1312
2359
  return;
1313
2360
  }
1314
2361
  }
1315
2362
  };
1316
- for (const node of compiled.ir.nodes) walk(node, IDENTITY, 1);
2363
+ for (const node of compiled.ir.nodes) walk(node, IDENTITY, 1, []);
1317
2364
  return ops;
1318
2365
  }
1319
2366
 
@@ -1364,29 +2411,29 @@ function sketchToTimeline(sketch, nodeIds) {
1364
2411
  const steps = [];
1365
2412
  events.forEach((ev, i) => {
1366
2413
  const node = nodeIds[i % nodeIds.length];
1367
- const dur2 = Math.max(0.05, ev.t1 - ev.t0);
2414
+ const dur3 = Math.max(0.05, ev.t1 - ev.t0);
1368
2415
  const ease = easeFor(ev.easing);
1369
2416
  let motion;
1370
2417
  switch (ev.kind) {
1371
2418
  case "enter":
1372
- motion = tween(node, { opacity: 1 }, { duration: dur2, ease });
2419
+ motion = tween(node, { opacity: 1 }, { duration: dur3, ease });
1373
2420
  break;
1374
2421
  case "exit":
1375
- motion = tween(node, { opacity: 0 }, { duration: dur2, ease });
2422
+ motion = tween(node, { opacity: 0 }, { duration: dur3, ease });
1376
2423
  break;
1377
2424
  case "emphasis": {
1378
2425
  const peak = 1 + Math.max(0.08, Math.min(0.5, ev.magnitude));
1379
2426
  motion = seq(
1380
- tween(node, { scale: peak }, { duration: dur2 / 2, ease: "easeOutCubic" }),
1381
- tween(node, { scale: 1 }, { duration: dur2 / 2, ease: "easeInOutQuad" })
2427
+ tween(node, { scale: peak }, { duration: dur3 / 2, ease: "easeOutCubic" }),
2428
+ tween(node, { scale: 1 }, { duration: dur3 / 2, ease: "easeInOutQuad" })
1382
2429
  );
1383
2430
  break;
1384
2431
  }
1385
2432
  case "scale":
1386
- motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur2, ease });
2433
+ motion = tween(node, { scale: 1 + Math.max(-0.5, Math.min(0.5, ev.magnitude)) }, { duration: dur3, ease });
1387
2434
  break;
1388
2435
  case "move":
1389
- motion = tween(node, { opacity: 1 }, { duration: dur2, ease });
2436
+ motion = tween(node, { opacity: 1 }, { duration: dur3, ease });
1390
2437
  break;
1391
2438
  }
1392
2439
  steps.push(ev.t0 > 0 ? seq(wait(ev.t0), motion) : motion);
@@ -1394,38 +2441,61 @@ function sketchToTimeline(sketch, nodeIds) {
1394
2441
  return par(...steps);
1395
2442
  }
1396
2443
  export {
2444
+ CHARACTER_PRESET_NAMES,
2445
+ DEFAULT_CROSSFADE,
1397
2446
  DEFAULT_FPS,
1398
2447
  DEFAULT_MOTIONPATH_DURATION,
1399
2448
  DEFAULT_TO_DURATION,
1400
2449
  DEFAULT_TWEEN_DURATION,
2450
+ DEVICE_PRESET_NAMES,
1401
2451
  EASE_NAMES,
2452
+ MOTION_OPS,
1402
2453
  PRESET_NAMES,
1403
2454
  PROPS_BY_TYPE,
1404
2455
  SFX_DURATION,
1405
2456
  SceneValidationError,
1406
2457
  beat,
2458
+ characterPreset,
1407
2459
  collectImageSrcs,
2460
+ compileComposition,
1408
2461
  compileScene,
1409
2462
  composeScene,
2463
+ composition,
2464
+ deviceBounds,
2465
+ devicePreset,
2466
+ deviceScreen,
2467
+ deviceScreenCenter,
1410
2468
  ellipse,
1411
2469
  evaluate,
2470
+ figure,
1412
2471
  formatComposeReport,
1413
2472
  group,
2473
+ humanoid,
2474
+ ikReach,
1414
2475
  image,
1415
2476
  isColor,
1416
2477
  lerpValue,
1417
2478
  line,
2479
+ motionOp,
2480
+ motionOpLabel,
1418
2481
  motionPath,
1419
2482
  motionPreset,
2483
+ nodeParentMatrix,
1420
2484
  oscillate,
2485
+ ovalPath,
1421
2486
  par,
1422
2487
  path,
1423
2488
  pathPoint,
1424
2489
  pathTangentAngle,
2490
+ poseTo,
1425
2491
  rect,
1426
2492
  resolveAudioPlan,
2493
+ resolveCompositionAudioPlan,
1427
2494
  resolveEase,
2495
+ rig,
2496
+ rigPose,
1428
2497
  sampleBehavior,
2498
+ sampleProp,
1429
2499
  scene,
1430
2500
  seq,
1431
2501
  sketchToTimeline,
@@ -1433,6 +2503,7 @@ export {
1433
2503
  text,
1434
2504
  to,
1435
2505
  tween,
2506
+ validateComposition,
1436
2507
  validateScene,
1437
2508
  wait,
1438
2509
  wiggle