@pixerate/editor 0.67.0 → 0.68.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.
@@ -222,7 +222,7 @@ var COMMAND_KEYS = {
222
222
  enter: [...BASE_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
223
223
  exit: [...BASE_KEYS, "style", "direction", "distance"],
224
224
  draw: [...BASE_KEYS, "from", "to"],
225
- move: [...BASE_KEYS, "by", "to", "blend"],
225
+ move: [...BASE_KEYS, "by", "to", "via", "pacing", "blend"],
226
226
  rotate: [...BASE_KEYS, "by", "to", "blend"],
227
227
  scale: [...BASE_KEYS, "to", "geometric", "blend"],
228
228
  fade: [...BASE_KEYS, "to"],
@@ -303,6 +303,9 @@ function commandShapeProblems(command2, where) {
303
303
  if (command2.by !== void 0 && command2.to !== void 0) p(`give "by" or "to", not both.`);
304
304
  if (command2.by !== void 0 && !isPoint(command2.by)) p(`"by" must be { "x": number, "y": number }.`);
305
305
  if (command2.to !== void 0 && !isPoint(command2.to)) p(`"to" must be { "x": number, "y": number }.`);
306
+ if (command2.via !== void 0 && !(Array.isArray(command2.via) && command2.via.length > 0 && command2.via.length <= 64 && command2.via.every(isPoint))) p(`"via" must be a list of 1 to 64 points { "x": number, "y": number } (document units).`);
307
+ if (command2.pacing !== void 0 && command2.pacing !== "even" && command2.pacing !== "equal") p(`"pacing" must be "even" (steady speed) or "equal" (each leg the same time).`);
308
+ if (command2.pacing !== void 0 && command2.via === void 0) p(`"pacing" goes with "via".`);
306
309
  break;
307
310
  case "rotate":
308
311
  if (command2.by === void 0 && command2.to === void 0) p(`give "by" or "to" (degrees).`);
@@ -503,6 +506,45 @@ function matchMotionNodes(from, into) {
503
506
  return out;
504
507
  }
505
508
 
509
+ // src/animation-editor/smooth.ts
510
+ function smoothMotionLegs(times, values) {
511
+ const n = values.length;
512
+ const slope = new Array(n).fill(0);
513
+ const secant = (i) => (values[i + 1] - values[i]) / Math.max(1e-9, times[i + 1] - times[i]);
514
+ for (let i = 1; i < n - 1; i++) {
515
+ const a = secant(i - 1);
516
+ const b = secant(i);
517
+ if (a * b <= 0) continue;
518
+ const ha = times[i] - times[i - 1];
519
+ const hb = times[i + 1] - times[i];
520
+ slope[i] = 3 * (ha + hb) / ((2 * hb + ha) / a + (hb + 2 * ha) / b);
521
+ }
522
+ const legs = [];
523
+ for (let i = 0; i + 1 < n; i++) {
524
+ const dt = times[i + 1] - times[i];
525
+ const dv = values[i + 1] - values[i];
526
+ if (Math.abs(dv) < 1e-9) {
527
+ legs.push({ start: times[i], end: times[i + 1], from: values[i], to: values[i + 1], ease: "linear" });
528
+ continue;
529
+ }
530
+ const y1 = slope[i] * dt / (3 * dv);
531
+ const y2 = 1 - slope[i + 1] * dt / (3 * dv);
532
+ legs.push({ start: times[i], end: times[i + 1], from: values[i], to: values[i + 1], ease: { type: "cubic-bezier", x1: 1 / 3, y1, x2: 2 / 3, y2 } });
533
+ }
534
+ return legs;
535
+ }
536
+ function smoothMotionTimes(points, start, duration, pacing = "even") {
537
+ const legs = points.slice(1).map((p, i) => pacing === "equal" ? 1 : Math.hypot(p.x - points[i].x, p.y - points[i].y));
538
+ const total = legs.reduce((a, b) => a + b, 0) || legs.length || 1;
539
+ const times = [start];
540
+ let acc = 0;
541
+ for (const l of legs) {
542
+ acc += total > 0 ? l : 1;
543
+ times.push(start + duration * acc / total);
544
+ }
545
+ return times;
546
+ }
547
+
506
548
  // src/animation-editor/compile.ts
507
549
  var indexCache = /* @__PURE__ */ new WeakMap();
508
550
  function indexMotionNodes(nodes) {
@@ -764,6 +806,7 @@ function motionCycleDuration(command2, scene, target) {
764
806
  const ms = length * MOTION_DRAW_MS_PER_UNIT;
765
807
  return Math.round(Math.min(MOTION_DRAW_RANGE[1], Math.max(MOTION_DRAW_RANGE[0], ms)));
766
808
  }
809
+ if (command2.type === "move" && _optionalChain([command2, 'access', _22 => _22.via, 'optionalAccess', _23 => _23.length])) return MOTION_DEFAULT_DURATIONS.move + 400 * command2.via.length;
767
810
  return MOTION_DEFAULT_DURATIONS[command2.type];
768
811
  }
769
812
  var MOTION_EASE_OUT = { type: "cubic-bezier", x1: 1 / 3, y1: 1, x2: 2 / 3, y2: 1 };
@@ -892,7 +935,7 @@ function compileMotionScene(scene) {
892
935
  const style = _nullishCoalesce(command2.style, () => ( "fade"));
893
936
  const opacity = rest("opacity");
894
937
  const reveal = style === "wipe" ? motionRevealClip(entry, { style, direction: command2.direction }) : style === "iris" ? motionRevealClip(entry, { style, shape: command2.shape }) : style === "split" ? motionRevealClip(entry, { style, orientation: command2.orientation }) : null;
895
- for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _22 => _22.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
938
+ for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _24 => _24.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
896
939
  if (reveal) {
897
940
  seg("opacity", opacity, "linear", start, start);
898
941
  seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
@@ -954,14 +997,28 @@ function compileMotionScene(scene) {
954
997
  }
955
998
  case "move": {
956
999
  const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
957
- let dx = _nullishCoalesce(_optionalChain([command2, 'access', _23 => _23.by, 'optionalAccess', _24 => _24.x]), () => ( 0));
958
- let dy = _nullishCoalesce(_optionalChain([command2, 'access', _25 => _25.by, 'optionalAccess', _26 => _26.y]), () => ( 0));
1000
+ let dx = _nullishCoalesce(_optionalChain([command2, 'access', _25 => _25.by, 'optionalAccess', _26 => _26.x]), () => ( 0));
1001
+ let dy = _nullishCoalesce(_optionalChain([command2, 'access', _27 => _27.by, 'optionalAccess', _28 => _28.y]), () => ( 0));
959
1002
  if (command2.to) {
960
1003
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
961
1004
  const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
962
1005
  dx = target.x - (entry.center.x + num("x"));
963
1006
  dy = target.y - (entry.center.y + num("y"));
964
1007
  }
1008
+ if (_optionalChain([command2, 'access', _29 => _29.via, 'optionalAccess', _30 => _30.length])) {
1009
+ const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
1010
+ const x0 = num("x");
1011
+ const y0 = num("y");
1012
+ const add = command2.blend === "add";
1013
+ const offsets = [{ x: 0, y: 0 }, ...command2.via.map((p) => _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, p)).map((p) => ({ x: p.x - entry.center.x - x0, y: p.y - entry.center.y - y0 })), { x: dx, y: dy }];
1014
+ const times = smoothMotionTimes(offsets, start, end - start, _nullishCoalesce(command2.pacing, () => ( "even")));
1015
+ for (const axis of ["x", "y"]) {
1016
+ const base = add ? 0 : axis === "x" ? x0 : y0;
1017
+ for (const leg of smoothMotionLegs(times, offsets.map((o) => o[axis])))
1018
+ seg(axis, base + leg.to, leg.ease, leg.start, leg.end, { from: base + leg.from, ...add ? { blend: "add" } : {} });
1019
+ }
1020
+ break;
1021
+ }
965
1022
  if (command2.blend === "add") {
966
1023
  seg("x", dx, e, start, end, { from: 0, blend: "add" });
967
1024
  seg("y", dy, e, start, end, { from: 0, blend: "add" });
@@ -1059,7 +1116,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
1059
1116
  var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
1060
1117
  var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
1061
1118
  function applyMotionControlValues(scene, values) {
1062
- if (!values || !_optionalChain([scene, 'access', _27 => _27.controls, 'optionalAccess', _28 => _28.length])) return scene;
1119
+ if (!values || !_optionalChain([scene, 'access', _31 => _31.controls, 'optionalAccess', _32 => _32.length])) return scene;
1063
1120
  const map = /* @__PURE__ */ new Map();
1064
1121
  for (const control of scene.controls) {
1065
1122
  const value = values[control.name];
@@ -1105,7 +1162,7 @@ function shapeIn(entry, shape) {
1105
1162
  }
1106
1163
  function shapeAt(entry, timeline, nodes, time) {
1107
1164
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
1108
- if (!_optionalChain([track, 'optionalAccess', _29 => _29.length])) return null;
1165
+ if (!_optionalChain([track, 'optionalAccess', _33 => _33.length])) return null;
1109
1166
  const s = activeSegment(track, time);
1110
1167
  const fromId = String(s ? s.from : track[0].from);
1111
1168
  if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
@@ -1128,8 +1185,8 @@ function motionFollowRoute(entry, path) {
1128
1185
  }
1129
1186
  function followAt(entry, timeline, nodes, time) {
1130
1187
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
1131
- const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _30 => _30[0]])));
1132
- if (!_optionalChain([s, 'optionalAccess', _31 => _31.path])) return null;
1188
+ const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _34 => _34[0]])));
1189
+ if (!_optionalChain([s, 'optionalAccess', _35 => _35.path])) return null;
1133
1190
  const route = motionFollowRoute(entry, nodes.get(s.path));
1134
1191
  const progress = Number(trackValue(track, time));
1135
1192
  if (!route || !(progress >= 0)) return null;
@@ -1189,7 +1246,7 @@ function motionFrameAt(scene, time, options = {}) {
1189
1246
  if (out.visible === void 0) delete out.visible;
1190
1247
  }
1191
1248
  const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
1192
- if (effectTracks.length && _optionalChain([out, 'access', _32 => _32.effects, 'optionalAccess', _33 => _33.length])) {
1249
+ if (effectTracks.length && _optionalChain([out, 'access', _36 => _36.effects, 'optionalAccess', _37 => _37.length])) {
1193
1250
  out = {
1194
1251
  ...out,
1195
1252
  effects: out.effects.map((effect) => {
@@ -1302,7 +1359,7 @@ function motionPath(scene, id, options = {}) {
1302
1359
  const r = (n) => Math.round(n * 100) / 100;
1303
1360
  const centreAt = (t) => {
1304
1361
  const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
1305
- const b = _optionalChain([node, 'optionalAccess', _34 => _34.bounds]);
1362
+ const b = _optionalChain([node, 'optionalAccess', _38 => _38.bounds]);
1306
1363
  return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
1307
1364
  };
1308
1365
  const points = [];
@@ -1342,14 +1399,14 @@ function motionStageSvg(scene, time, options = {}) {
1342
1399
  const open = current.indexOf(">") + 1;
1343
1400
  const close = current.lastIndexOf("</svg>");
1344
1401
  const body = current.slice(open, close);
1345
- const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _35 => _35.exec, 'call', _36 => _36(body), 'optionalAccess', _37 => _37[0]]), () => ( "")) : "";
1402
+ const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _39 => _39.exec, 'call', _40 => _40(body), 'optionalAccess', _41 => _41[0]]), () => ( "")) : "";
1346
1403
  return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
1347
1404
  }
1348
1405
 
1349
1406
  // src/animation-editor/check.ts
1350
1407
  var MOTION_MIN_CHANGE_MS = 120;
1351
1408
  var NUMERIC_PARAMS = (node, effectId) => {
1352
- const effect = _optionalChain([node, 'access', _38 => _38.effects, 'optionalAccess', _39 => _39.find, 'call', _40 => _40((e) => e.id === effectId)]);
1409
+ const effect = _optionalChain([node, 'access', _42 => _42.effects, 'optionalAccess', _43 => _43.find, 'call', _44 => _44((e) => e.id === effectId)]);
1353
1410
  if (!effect) return null;
1354
1411
  return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
1355
1412
  };
@@ -1501,7 +1558,7 @@ function checkMotionScene(scene) {
1501
1558
  if (scene.trigger) {
1502
1559
  if ((_nullishCoalesce(scene.loop, () => ( "none"))) !== "none") issues.push(motionIssue("trigger-loop", `The scene plays on ${scene.trigger}, so its "${scene.loop}" loop does not apply; set loop to "none" or remove the trigger.`, {}));
1503
1560
  if (scene.trigger === "click") {
1504
- if (scene.shots.length === 1) issues.push(motionIssue("click-one-shot", "The scene plays on click but has one shot, so every click replays it from the start; split it into shots (the first plays on load, each click plays the next).", { shot: scene.shots[0].id }));
1561
+ if (scene.shots.length === 1) issues.push(motionIssue("click-one-shot", "The scene plays on click but has one shot, so every click replays it from the start; split it into shots (each click plays the next one).", { shot: scene.shots[0].id }));
1505
1562
  scene.shots.forEach((shot, i) => {
1506
1563
  if (i > 0 && shot.at) issues.push(motionIssue("click-cue", `Shot "${shot.id}" is cued, but on click a shot starts when it is clicked to; remove its "at" or the trigger.`, { shot: shot.id }));
1507
1564
  });
@@ -1511,7 +1568,7 @@ function checkMotionScene(scene) {
1511
1568
  if (!shot.at) return;
1512
1569
  const timing = timeline.shots[i];
1513
1570
  if (timing.cue === void 0) {
1514
- const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${_nullishCoalesce(_optionalChain([scene, 'access', _41 => _41.narration, 'optionalAccess', _42 => _42.words, 'access', _43 => _43.length]), () => ( 0))} words (indexes from 0)`;
1571
+ const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${_nullishCoalesce(_optionalChain([scene, 'access', _45 => _45.narration, 'optionalAccess', _46 => _46.words, 'access', _47 => _47.length]), () => ( 0))} words (indexes from 0)`;
1515
1572
  issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
1516
1573
  } else if (timing.missedBy !== void 0) {
1517
1574
  const late = Math.round(timing.missedBy);
@@ -1544,7 +1601,7 @@ function checkMotionScene(scene) {
1544
1601
  const cam = frame.camera;
1545
1602
  const docIndex = indexMotionNodes(frame.document.nodes);
1546
1603
  for (const id of command2.targets) {
1547
- const b = _optionalChain([docIndex, 'access', _44 => _44.get, 'call', _45 => _45(id), 'optionalAccess', _46 => _46.bounds]);
1604
+ const b = _optionalChain([docIndex, 'access', _48 => _48.get, 'call', _49 => _49(id), 'optionalAccess', _50 => _50.bounds]);
1548
1605
  if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
1549
1606
  const outside = b.x + b.width < cam.x || b.y + b.height < cam.y || b.x > cam.x + cam.width || b.y > cam.y + cam.height;
1550
1607
  if (outside) {
@@ -1750,9 +1807,9 @@ function applyMotionEdits(scene, edits) {
1750
1807
  let next = scene;
1751
1808
  for (let i = 0; i < list.length; i++) {
1752
1809
  const edit = list[i];
1753
- const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _47 => _47.op])}): ` : "";
1810
+ const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _51 => _51.op])}): ` : "";
1754
1811
  if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
1755
- return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _48 => _48.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1812
+ return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _52 => _52.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1756
1813
  try {
1757
1814
  next = applyOne(next, edit);
1758
1815
  } catch (error) {
@@ -1912,7 +1969,7 @@ function toFlairSvgMotion(scene, options = {}) {
1912
1969
  const wrapper = freshId(`${id}--follow`);
1913
1970
  const centre = freshId(`${id}--follow-centre`);
1914
1971
  const c = entry.center;
1915
- const at = _optionalChain([motionNodeOffset, 'call', _49 => _49(entry, timeline, 0, nodes), 'optionalAccess', _50 => _50.follow]);
1972
+ const at = _optionalChain([motionNodeOffset, 'call', _53 => _53(entry, timeline, 0, nodes), 'optionalAccess', _54 => _54.follow]);
1916
1973
  const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
1917
1974
  (wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
1918
1975
  follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
@@ -1972,7 +2029,7 @@ function toFlairSvgMotion(scene, options = {}) {
1972
2029
  exact: () => false,
1973
2030
  value: (t) => {
1974
2031
  const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
1975
- const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _51 => _51(frame.document.nodes), 'access', _52 => _52.get, 'call', _53 => _53(id), 'optionalAccess', _54 => _54.node]));
2032
+ const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _55 => _55(frame.document.nodes), 'access', _56 => _56.get, 'call', _57 => _57(id), 'optionalAccess', _58 => _58.node]));
1976
2033
  if (!now || now.type !== "path") return "";
1977
2034
  const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
1978
2035
  return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
@@ -1993,8 +2050,8 @@ function toFlairSvgMotion(scene, options = {}) {
1993
2050
  ...delay > 0 ? { delay: round2(delay) } : {},
1994
2051
  ms: round2(ms),
1995
2052
  ease: written,
1996
- ..._optionalChain([segment, 'optionalAccess', _55 => _55.behavior]) ? { behavior: segment.behavior } : {},
1997
- ..._optionalChain([segment, 'optionalAccess', _56 => _56.steps]) ? { strobe: segment.steps } : {}
2053
+ ..._optionalChain([segment, 'optionalAccess', _59 => _59.behavior]) ? { behavior: segment.behavior } : {},
2054
+ ..._optionalChain([segment, 'optionalAccess', _60 => _60.steps]) ? { strobe: segment.steps } : {}
1998
2055
  };
1999
2056
  if (channel.kind === "view") animate.push({ ...base, view: to });
2000
2057
  else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
@@ -2065,7 +2122,7 @@ function toFlairSvgMotion(scene, options = {}) {
2065
2122
  animate.sort((x, y) => (_nullishCoalesce(x.delay, () => ( 0))) - (_nullishCoalesce(y.delay, () => ( 0))));
2066
2123
  const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
2067
2124
  const trigger = options.trigger === null ? void 0 : _nullishCoalesce(options.trigger, () => ( scene.trigger));
2068
- if (trigger === "click" && timeline.shots.length > 1) {
2125
+ if (trigger === "click" && timeline.shots.length) {
2069
2126
  for (const a of animate) {
2070
2127
  const at = _nullishCoalesce(a.delay, () => ( 0));
2071
2128
  let step = 0;
@@ -2075,7 +2132,7 @@ function toFlairSvgMotion(scene, options = {}) {
2075
2132
  const delay = round2(at - timeline.shots[step].start);
2076
2133
  delete a.delay;
2077
2134
  if (delay > 0) a.delay = delay;
2078
- if (step > 0) a.step = step;
2135
+ a.step = step + 1;
2079
2136
  }
2080
2137
  animate.sort((x, y) => (_nullishCoalesce(x.step, () => ( 0))) - (_nullishCoalesce(y.step, () => ( 0))) || (_nullishCoalesce(x.delay, () => ( 0))) - (_nullishCoalesce(y.delay, () => ( 0))));
2081
2138
  }
@@ -2083,7 +2140,7 @@ function toFlairSvgMotion(scene, options = {}) {
2083
2140
  let svg = _chunkYJLCBA7Vcjs.vectorDocumentToSvg.call(void 0, { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) }, { pretty: false, precision, wrap });
2084
2141
  if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
2085
2142
  let motion = { svg, animate };
2086
- if (_optionalChain([scene, 'access', _57 => _57.controls, 'optionalAccess', _58 => _58.length])) {
2143
+ if (_optionalChain([scene, 'access', _61 => _61.controls, 'optionalAccess', _62 => _62.length])) {
2087
2144
  const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
2088
2145
  const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
2089
2146
  svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
@@ -2105,7 +2162,7 @@ function toFlairVectorEffect(scene, options = {}) {
2105
2162
  tags: _nullishCoalesce(options.tags, () => ( [])),
2106
2163
  motion: out.motion,
2107
2164
  hold: out.hold,
2108
- ..._optionalChain([scene, 'access', _59 => _59.presets, 'optionalAccess', _60 => _60.length]) ? { presets: scene.presets } : {},
2165
+ ..._optionalChain([scene, 'access', _63 => _63.presets, 'optionalAccess', _64 => _64.length]) ? { presets: scene.presets } : {},
2109
2166
  ...out.trigger ? { trigger: out.trigger } : {}
2110
2167
  };
2111
2168
  }
@@ -2149,7 +2206,7 @@ function resolvePlaceholders(motion, notes) {
2149
2206
  const filled = text.replace(PLACEHOLDER, (_, name) => {
2150
2207
  const d = declared.get(name);
2151
2208
  if (!d) unknown.add(name);
2152
- return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _61 => _61.value]), () => ( ""))).replace(/"/g, '\\"');
2209
+ return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _65 => _65.value]), () => ( ""))).replace(/"/g, '\\"');
2153
2210
  });
2154
2211
  for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
2155
2212
  return { motion: JSON.parse(filled), controls };
@@ -2203,7 +2260,7 @@ function commandsFor(anim, nodes, entered, notes) {
2203
2260
  if (anim.along) {
2204
2261
  const path = nodes.get(anim.along);
2205
2262
  if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
2206
- else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _62 => _62.at, 'optionalAccess', _63 => _63[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _64 => _64.at, 'optionalAccess', _65 => _65[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2263
+ else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _66 => _66.at, 'optionalAccess', _67 => _67[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _68 => _68.at, 'optionalAccess', _69 => _69[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2207
2264
  }
2208
2265
  for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
2209
2266
  if (attr === "opacity") {
@@ -2214,7 +2271,7 @@ function commandsFor(anim, nodes, entered, notes) {
2214
2271
  out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
2215
2272
  } else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
2216
2273
  } else if (attr === "fill" || attr === "stroke") {
2217
- if (_optionalChain([behavior, 'optionalAccess', _66 => _66.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2274
+ if (_optionalChain([behavior, 'optionalAccess', _70 => _70.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2218
2275
  else {
2219
2276
  if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
2220
2277
  out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
@@ -2260,7 +2317,8 @@ function fromFlairSvgMotion(input, options = {}) {
2260
2317
  const nodes = indexMotionNodes(artwork.nodes);
2261
2318
  const byStep = /* @__PURE__ */ new Map();
2262
2319
  for (const anim of _nullishCoalesce(motion.animate, () => ( []))) {
2263
- const step = Math.max(0, Math.floor(_nullishCoalesce(anim.step, () => ( 0))));
2320
+ const raw = Math.max(0, Math.floor(_nullishCoalesce(anim.step, () => ( 0))));
2321
+ const step = options.clickSteps ? Math.max(1, raw) : raw;
2264
2322
  if (!byStep.has(step)) byStep.set(step, []);
2265
2323
  byStep.get(step).push(anim);
2266
2324
  }
@@ -2311,7 +2369,7 @@ function presetColor(value) {
2311
2369
  }
2312
2370
  function fromFlairVectorEffect(effect, options = {}) {
2313
2371
  if (!effect || effect.kind !== "vector" || !effect.motion) throw new Error('Give a Flair vector effect: { kind: "vector", id, name, motion: { svg, animate } }.');
2314
- const result = fromFlairSvgMotion(effect.motion, { name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( effect.id)), () => ( effect.name)), hold: _nullishCoalesce(options.hold, () => ( effect.hold)) });
2372
+ const result = fromFlairSvgMotion(effect.motion, { name: _nullishCoalesce(_nullishCoalesce(options.name, () => ( effect.id)), () => ( effect.name)), hold: _nullishCoalesce(options.hold, () => ( effect.hold)), ...effect.trigger === "click" ? { clickSteps: true } : {} });
2315
2373
  const names = new Set((_nullishCoalesce(result.scene.controls, () => ( []))).map((c) => c.name));
2316
2374
  const presets = (_nullishCoalesce(effect.presets, () => ( []))).map((p) => ({ name: p.name, values: Object.fromEntries(Object.entries(p.values).flatMap(([k, v]) => names.has(k) && presetColor(v) ? [[k, presetColor(v)]] : [])) })).filter((p) => Object.keys(p.values).length);
2317
2375
  const scene = { ...result.scene, ...effect.description ? { description: effect.description } : {}, ...presets.length ? { presets } : {}, ...effect.trigger ? { trigger: effect.trigger } : {} };
@@ -2496,7 +2554,7 @@ function toLottie(scene, options = {}) {
2496
2554
  const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
2497
2555
  const geometryAt = (t) => {
2498
2556
  if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2499
- const now = _optionalChain([indexMotionNodes, 'call', _67 => _67(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _68 => _68.get, 'call', _69 => _69(path.id), 'optionalAccess', _70 => _70.node]);
2557
+ const now = _optionalChain([indexMotionNodes, 'call', _71 => _71(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _72 => _72.get, 'call', _73 => _73(path.id), 'optionalAccess', _74 => _74.node]);
2500
2558
  return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2501
2559
  };
2502
2560
  const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
@@ -2542,7 +2600,7 @@ function toLottie(scene, options = {}) {
2542
2600
  lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
2543
2601
  ml: 4
2544
2602
  });
2545
- if (_optionalChain([path, 'access', _71 => _71.strokeDasharray, 'optionalAccess', _72 => _72.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2603
+ if (_optionalChain([path, 'access', _75 => _75.strokeDasharray, 'optionalAccess', _76 => _76.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2546
2604
  if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
2547
2605
  }
2548
2606
  const fill = paintOf(path.fill, "fill", path.id);
@@ -2693,7 +2751,7 @@ var AnimationEditorController = (_class = class {
2693
2751
  time: p.time,
2694
2752
  playing: p.playing,
2695
2753
  loop: p.loop,
2696
- currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _73 => _73(timeline, p.time), 'optionalAccess', _74 => _74.id]), () => ( null)),
2754
+ currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _77 => _77(timeline, p.time), 'optionalAccess', _78 => _78.id]), () => ( null)),
2697
2755
  selection: this.selection,
2698
2756
  readOnly: this.readOnly,
2699
2757
  error: this.error,
@@ -2732,7 +2790,7 @@ var AnimationEditorController = (_class = class {
2732
2790
  this.applying = false;
2733
2791
  }
2734
2792
  this.notify();
2735
- _optionalChain([this, 'access', _75 => _75.onChange, 'optionalCall', _76 => _76(scene)]);
2793
+ _optionalChain([this, 'access', _79 => _79.onChange, 'optionalCall', _80 => _80(scene)]);
2736
2794
  }
2737
2795
  commit(label, next) {
2738
2796
  const before = this.scene;
@@ -2936,7 +2994,7 @@ var MOTION_COMMAND_SCHEMA = {
2936
2994
  distance: { type: "number", description: "For slide: document units (default 40)." }
2937
2995
  }),
2938
2996
  command("draw", "Draws strokes on (or off) to a fraction of their length.", { to: FRACTION("Fraction drawn at the end (default 1)."), from: FRACTION("Fraction drawn at the start (default: what is drawn now).") }),
2939
- command("move", "Moves targets by an offset or so their centre lands on a point.", { blend: BLEND("replace"), by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
2997
+ command("move", "Moves targets by an offset or so their centre lands on a point.", { via: { type: "array", items: POINT("A point the centre passes through."), minItems: 1, maxItems: 64, description: 'Waypoints (document units) the centre passes through on the way to "to" or "by", smoothly (velocity carries through each) and without overshooting any. Default length 700 ms + 400 per waypoint.' }, pacing: { type: "string", enum: ["even", "equal"], description: "With via: even (default) keeps the speed steady along the path; equal gives each stretch the same time." }, blend: BLEND("replace"), by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
2940
2998
  command("rotate", "Turns targets about their centre (degrees, clockwise).", { blend: BLEND("replace"), by: { type: "number", description: "Degrees to turn by." }, to: { type: "number", description: "Angle to turn to, in degrees from rest." } }),
2941
2999
  command("scale", "Scales targets about their centre (1 = rest size).", { blend: BLEND("replace"), to: { description: "A factor, or { x, y } factors.", anyOf: [{ type: "number" }, POINT("Factors per axis.")] }, geometric: { type: "boolean", description: "Grow by a steady factor, which reads as an even zoom (default false)." } }, ["to"]),
2942
3000
  command("fade", "Changes opacity.", { to: FRACTION("Opacity at the end (0\u20131).") }, ["to"]),
@@ -3025,7 +3083,7 @@ var SETTINGS = {
3025
3083
  loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
3026
3084
  trigger: {
3027
3085
  anyOf: [{ type: "string", enum: ["click", "hover", "press", "scroll"] }, { type: "null" }],
3028
- description: "Play in answer to the viewer when exported to Flair: click (the first shot plays on load, each click plays the next shot), hover or press (plays while the pointer is over or down, rewinds when it leaves), scroll (scrubs as it scrolls past). null plays on its own (default). The editor previews it played through."
3086
+ description: "Play in answer to the viewer when exported to Flair: click (nothing plays until a click; each click plays the next shot, then the first again), hover or press (plays while the pointer is over or down, rewinds when it leaves), scroll (scrubs as it scrolls past). null plays on its own (default). The editor previews it played through."
3029
3087
  },
3030
3088
  fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
3031
3089
  steps: { anyOf: [{ type: "number", exclusiveMinimum: 0, maximum: 120 }, { type: "null" }], description: 'Stepped time for every command without its own: changes hold on a grid of this many frames per second (12 = "on twos" at 24 fps) for a hand-drawn look; null makes it smooth again (default).' },
@@ -3254,9 +3312,9 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3254
3312
  handler: ({ from = 0, to, limit = 200 }) => {
3255
3313
  const s = scene();
3256
3314
  const timeline = compileMotionScene(s);
3257
- const words = _nullishCoalesce(_optionalChain([s, 'access', _77 => _77.narration, 'optionalAccess', _78 => _78.words]), () => ( []));
3315
+ const words = _nullishCoalesce(_optionalChain([s, 'access', _81 => _81.narration, 'optionalAccess', _82 => _82.words]), () => ( []));
3258
3316
  const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
3259
- const shotAt = (ms) => _optionalChain([timeline, 'access', _79 => _79.shots, 'access', _80 => _80.find, 'call', _81 => _81((x) => ms >= x.start && ms < x.end), 'optionalAccess', _82 => _82.id]);
3317
+ const shotAt = (ms) => _optionalChain([timeline, 'access', _83 => _83.shots, 'access', _84 => _84.find, 'call', _85 => _85((x) => ms >= x.start && ms < x.end), 'optionalAccess', _86 => _86.id]);
3260
3318
  const beats = [];
3261
3319
  if (s.beat && s.beat.bpm > 0) {
3262
3320
  const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
@@ -3444,7 +3502,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3444
3502
  inputSchema: object({ shot: SHOT_ID, commands: { type: "array", items: MOTION_COMMAND_SCHEMA, minItems: 1, description: "Commands to add." }, at: { type: "integer", minimum: 0, description: "Index in the run (default: the end)." } }, ["shot", "commands"]),
3445
3503
  annotations: { title: "Add commands to a shot", ...WRITE },
3446
3504
  handler: ({ shot, commands, at }) => {
3447
- const before = _nullishCoalesce(_optionalChain([scene, 'call', _83 => _83(), 'access', _84 => _84.shots, 'access', _85 => _85.find, 'call', _86 => _86((s) => s.id === shot), 'optionalAccess', _87 => _87.run, 'access', _88 => _88.length]), () => ( 0));
3505
+ const before = _nullishCoalesce(_optionalChain([scene, 'call', _87 => _87(), 'access', _88 => _88.shots, 'access', _89 => _89.find, 'call', _90 => _90((s) => s.id === shot), 'optionalAccess', _91 => _91.run, 'access', _92 => _92.length]), () => ( 0));
3448
3506
  const first = _nullishCoalesce(at, () => ( before));
3449
3507
  return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
3450
3508
  const shots = describeMotionShots(s);
@@ -3606,6 +3664,7 @@ Commands
3606
3664
  - enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.
3607
3665
  - enter also reveals through a clip over the target's box: wipe (behind an edge travelling "direction", default right), iris (from the centre; "shape": circle or box) and split (from a line through the middle; "orientation": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.
3608
3666
  - draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.
3667
+ - move takes "via" waypoints for a curved, smooth path that passes exactly through each point (steady speed by default); use it instead of chaining moves, which stop at each point.
3609
3668
  - enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
3610
3669
  - morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
3611
3670
  - Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or "axis": "vertical"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).
@@ -3621,7 +3680,7 @@ Working method
3621
3680
  1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).
3622
3681
  2. Plan a few shots, each one idea. Add them with insert_shots (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).
3623
3682
  3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.
3624
- - For something the viewer plays (a button, a toggle, a step-by-step explainer), set "trigger" with set_animation_settings: click plays the next shot on each click (make the last shot lead back to how the first ended for a toggle), hover or press plays and rewinds, scroll scrubs.
3683
+ - For something the viewer plays (a button, a toggle, a step-by-step explainer), set "trigger" with set_animation_settings: click plays the next shot on each click and then starts over (make the last shot put things back for a toggle), hover or press plays and rewinds, scroll scrubs.
3625
3684
  4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
3626
3685
 
3627
3686
  Keep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.`;
@@ -3631,7 +3690,7 @@ var TIMING = /* @__PURE__ */ new Set(["duration", "delay", "stagger", "with", "e
3631
3690
  var COLORS = /* @__PURE__ */ new Set(["fill", "stroke", "color"]);
3632
3691
  var NODES = /* @__PURE__ */ new Set(["into", "path"]);
3633
3692
  function branch(type) {
3634
- return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _89 => _89.properties, 'optionalAccess', _90 => _90.type, 'optionalAccess', _91 => _91.const]) === type);
3693
+ return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _93 => _93.properties, 'optionalAccess', _94 => _94.type, 'optionalAccess', _95 => _95.const]) === type);
3635
3694
  }
3636
3695
  function unitOf(type, name) {
3637
3696
  if (name === "duration" || name === "delay" || name === "stagger") return "ms";
@@ -3666,7 +3725,7 @@ function kindOf(name, schema) {
3666
3725
  }
3667
3726
  function motionCommandFields(type) {
3668
3727
  const schema = branch(type);
3669
- if (!_optionalChain([schema, 'optionalAccess', _92 => _92.properties])) return [];
3728
+ if (!_optionalChain([schema, 'optionalAccess', _96 => _96.properties])) return [];
3670
3729
  const required = new Set(_nullishCoalesce(schema.required, () => ( [])));
3671
3730
  const fields = Object.entries(schema.properties).filter(([name]) => name !== "type").map(([name, s]) => {
3672
3731
  const kind = kindOf(name, s);
@@ -3723,7 +3782,7 @@ function defaultMotionCommand(type, targets, scene) {
3723
3782
  if (type === "wait") return { type: "wait", duration: 500 };
3724
3783
  if (!targets.length) return { error: "Pick a target first: click an element on the stage." };
3725
3784
  const nodes = [...indexMotionNodes(scene.artwork.nodes).values()];
3726
- const other = (pred) => _optionalChain([nodes, 'access', _93 => _93.find, 'call', _94 => _94((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _95 => _95.node, 'access', _96 => _96.id]);
3785
+ const other = (pred) => _optionalChain([nodes, 'access', _97 => _97.find, 'call', _98 => _98((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _99 => _99.node, 'access', _100 => _100.id]);
3727
3786
  const base = { targets: [...targets] };
3728
3787
  switch (type) {
3729
3788
  case "scale":
@@ -3750,8 +3809,8 @@ function defaultMotionCommand(type, targets, scene) {
3750
3809
  }
3751
3810
  case "effect": {
3752
3811
  for (const id of targets) {
3753
- const node = _optionalChain([nodes, 'access', _97 => _97.find, 'call', _98 => _98((e) => e.node.id === id), 'optionalAccess', _99 => _99.node]);
3754
- for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _100 => _100.effects]), () => ( []))) {
3812
+ const node = _optionalChain([nodes, 'access', _101 => _101.find, 'call', _102 => _102((e) => e.node.id === id), 'optionalAccess', _103 => _103.node]);
3813
+ for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _104 => _104.effects]), () => ( []))) {
3755
3814
  const param = Object.entries(effect.params).find(([, v]) => typeof v === "number");
3756
3815
  if (param) return { type, ...base, effect: effect.id, param: param[0], to: param[1] + 1 };
3757
3816
  }
@@ -3776,11 +3835,11 @@ function motionCommandRows(scene, shotId) {
3776
3835
  return scene.shots[index].run.map((command2, i) => {
3777
3836
  const t = timeline.commands.find((c) => c.shotIndex === index && c.command === i);
3778
3837
  const primitive = motionFlairPrimitive(command2);
3779
- return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _101 => _101.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _102 => _102.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
3838
+ return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _105 => _105.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _106 => _106.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
3780
3839
  });
3781
3840
  }
3782
3841
  function duplicateMotionCommandEdit(scene, shot, command2) {
3783
- const run = _nullishCoalesce(_optionalChain([scene, 'access', _103 => _103.shots, 'access', _104 => _104.find, 'call', _105 => _105((s) => s.id === shot), 'optionalAccess', _106 => _106.run]), () => ( []));
3842
+ const run = _nullishCoalesce(_optionalChain([scene, 'access', _107 => _107.shots, 'access', _108 => _108.find, 'call', _109 => _109((s) => s.id === shot), 'optionalAccess', _110 => _110.run]), () => ( []));
3784
3843
  return { op: "add_commands", shot, commands: run[command2] ? [structuredClone(run[command2])] : [], at: command2 + 1 };
3785
3844
  }
3786
3845
  var MOTION_EASE_CHOICES = [
@@ -3865,7 +3924,7 @@ function motionCueFromKey(key) {
3865
3924
  function motionCueChoices(scene, limit = 256) {
3866
3925
  const choices = [];
3867
3926
  const s = (ms) => `${(ms / 1e3).toFixed(2)} s`;
3868
- const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _107 => _107.narration, 'optionalAccess', _108 => _108.words, 'access', _109 => _109.map, 'call', _110 => _110((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
3927
+ const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _111 => _111.narration, 'optionalAccess', _112 => _112.words, 'access', _113 => _113.map, 'call', _114 => _114((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
3869
3928
  if (scene.beat && scene.beat.bpm > 0) {
3870
3929
  for (let beat = 0; choices.length < limit; beat++) {
3871
3930
  const time = motionBeatTime(scene.beat, beat);
@@ -3875,7 +3934,7 @@ function motionCueChoices(scene, limit = 256) {
3875
3934
  choices.push({ value: `beat:${beat}`, label: `Beat ${beat} (bar ${at.bar}.${at.beatInBar}) \xB7 ${s(time)}`, time });
3876
3935
  }
3877
3936
  }
3878
- _optionalChain([scene, 'access', _111 => _111.narration, 'optionalAccess', _112 => _112.words, 'access', _113 => _113.forEach, 'call', _114 => _114((w, i) => {
3937
+ _optionalChain([scene, 'access', _115 => _115.narration, 'optionalAccess', _116 => _116.words, 'access', _117 => _117.forEach, 'call', _118 => _118((w, i) => {
3879
3938
  if (choices.length < limit * 2) choices.push({ value: `word:${i}`, label: `Word ${i} \u201C${w.text}\u201D \xB7 ${s(w.start)}`, time: w.start });
3880
3939
  })]);
3881
3940
  return choices;
@@ -3884,7 +3943,7 @@ function motionShotCueTime(scene, cue) {
3884
3943
  return cue ? motionCueTime(scene, cue) : void 0;
3885
3944
  }
3886
3945
  function formatMotionNarration(narration) {
3887
- return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _115 => _115.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
3946
+ return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _119 => _119.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
3888
3947
  }
3889
3948
  function parseMotionNarration(text) {
3890
3949
  const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
@@ -4019,5 +4078,7 @@ function parseMotionNarration(text) {
4019
4078
 
4020
4079
 
4021
4080
 
4022
- exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_CHOICES = MOTION_EASE_CHOICES; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MOTION_TRIGGERS = MOTION_TRIGGERS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.defaultMotionCommand = defaultMotionCommand; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.duplicateMotionCommandEdit = duplicateMotionCommandEdit; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.fitMotionSceneEdits = fitMotionSceneEdits; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.formatMotionNarration = formatMotionNarration; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.highlightMotionSvg = highlightMotionSvg; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionCueChoices = motionCueChoices; exports.motionCueFromKey = motionCueFromKey; exports.motionCueKey = motionCueKey; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionEaseKey = motionEaseKey; exports.motionEasingCurve = motionEasingCurve; exports.motionEasingGuides = motionEasingGuides; exports.motionEasingPath = motionEasingPath; exports.motionFieldEdit = motionFieldEdit; exports.motionFieldLabel = motionFieldLabel; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; exports.motionNodeAtElement = motionNodeAtElement; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPath = motionPath; exports.motionPathLength = motionPathLength; exports.motionPathsSvg = motionPathsSvg; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionRulerTicks = motionRulerTicks; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotCueTime = motionShotCueTime; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionFieldInput = parseMotionFieldInput; exports.parseMotionNarration = parseMotionNarration; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
4081
+
4082
+
4083
+ exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_CHOICES = MOTION_EASE_CHOICES; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MOTION_TRIGGERS = MOTION_TRIGGERS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.defaultMotionCommand = defaultMotionCommand; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.duplicateMotionCommandEdit = duplicateMotionCommandEdit; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.fitMotionSceneEdits = fitMotionSceneEdits; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.formatMotionNarration = formatMotionNarration; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.highlightMotionSvg = highlightMotionSvg; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionCueChoices = motionCueChoices; exports.motionCueFromKey = motionCueFromKey; exports.motionCueKey = motionCueKey; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionEaseKey = motionEaseKey; exports.motionEasingCurve = motionEasingCurve; exports.motionEasingGuides = motionEasingGuides; exports.motionEasingPath = motionEasingPath; exports.motionFieldEdit = motionFieldEdit; exports.motionFieldLabel = motionFieldLabel; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; exports.motionNodeAtElement = motionNodeAtElement; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPath = motionPath; exports.motionPathLength = motionPathLength; exports.motionPathsSvg = motionPathsSvg; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionRulerTicks = motionRulerTicks; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotCueTime = motionShotCueTime; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionFieldInput = parseMotionFieldInput; exports.parseMotionNarration = parseMotionNarration; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.smoothMotionLegs = smoothMotionLegs; exports.smoothMotionTimes = smoothMotionTimes; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
4023
4084
  //# sourceMappingURL=index.cjs.map