@pixerate/editor 0.69.0 → 0.71.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.
@@ -223,10 +223,11 @@ var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "clips",
223
223
  var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
224
224
  var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
225
225
  var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
226
+ var DRAW_ORDER_KEYS = ["order", "pace", "align", "sortBy", "origin"];
226
227
  var COMMAND_KEYS = {
227
- enter: [...BASE_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
228
- exit: [...BASE_KEYS, "style", "direction", "distance"],
229
- draw: [...BASE_KEYS, "from", "to"],
228
+ enter: [...BASE_KEYS, ...DRAW_ORDER_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
229
+ exit: [...BASE_KEYS, ...DRAW_ORDER_KEYS, "style", "direction", "distance"],
230
+ draw: [...BASE_KEYS, ...DRAW_ORDER_KEYS, "from", "to", "comet"],
230
231
  move: [...BASE_KEYS, "by", "to", "via", "pacing", "blend"],
231
232
  rotate: [...BASE_KEYS, "by", "to", "blend"],
232
233
  scale: [...BASE_KEYS, "to", "geometric", "blend"],
@@ -270,6 +271,14 @@ function commandShapeProblems(command2, where) {
270
271
  if (command2[key] !== void 0 && !isNum(command2[key])) p(`"${key}" must be a number.`);
271
272
  };
272
273
  if (command2.with !== void 0 && command2.with !== "after" && command2.with !== "previous") p(`"with" must be "after" or "previous".`);
274
+ if (DRAW_ORDER_KEYS.some((k) => command2[k] !== void 0)) {
275
+ if (type === "enter" && command2.style !== "draw" || type === "exit" && command2.style !== "undraw") p(`"order", "pace", "align", "sortBy" and "origin" go with drawing: style "${type === "enter" ? "draw" : "undraw"}".`);
276
+ if (command2.order !== void 0 && command2.order !== "together" && command2.order !== "sequence") p(`"order" must be "together" or "sequence" (one stroke after another).`);
277
+ if (command2.pace !== void 0 && command2.pace !== "time" && command2.pace !== "speed") p(`"pace" must be "time" (each stroke takes the whole time) or "speed" (all at one speed).`);
278
+ if (command2.align !== void 0 && command2.align !== "start" && command2.align !== "end") p(`"align" must be "start" or "end".`);
279
+ if (command2.sortBy !== void 0 && command2.sortBy !== "document" && command2.sortBy !== "distance") p(`"sortBy" must be "document" or "distance".`);
280
+ if (command2.origin !== void 0 && !isPoint(command2.origin)) p(`"origin" must be { "x": number, "y": number } (document units).`);
281
+ }
273
282
  if (command2.note !== void 0 && typeof command2.note !== "string") p(`"note" must be a string.`);
274
283
  if (type === "wait") {
275
284
  if (!isNum(command2.duration) || command2.duration < 0) p(`"duration" must be a number of milliseconds \u2265 0.`);
@@ -303,6 +312,8 @@ function commandShapeProblems(command2, where) {
303
312
  case "draw":
304
313
  fraction("from");
305
314
  fraction("to");
315
+ fraction("comet");
316
+ if (command2.comet !== void 0 && (command2.from !== void 0 || command2.to !== void 0)) p(`"comet" runs the whole path; leave out "from" and "to".`);
306
317
  break;
307
318
  case "move":
308
319
  if (command2.by === void 0 && command2.to === void 0) p(`give "by" (an offset) or "to" (a point).`);
@@ -1003,19 +1014,58 @@ function compileMotionScene(scene) {
1003
1014
  return typeof v === "number" ? v : 0;
1004
1015
  };
1005
1016
  const ease = command2.ease;
1017
+ const drawWindows = (s, e, o) => {
1018
+ const order2 = _nullishCoalesce(o.order, () => ( "together"));
1019
+ const pace = _nullishCoalesce(o.pace, () => ( "time"));
1020
+ if (order2 === "together" && pace === "time") return null;
1021
+ const paths = entry.node.type === "path" ? [entry.node] : _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, entry.node);
1022
+ let list = paths.map((p) => {
1023
+ const at = nodes.get(p.id);
1024
+ let first = { x: 0, y: 0 };
1025
+ try {
1026
+ const m = _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, p, _nullishCoalesce(_optionalChain([at, 'optionalAccess', _27 => _27.parentWorld]), () => ( _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)), { effects: false })[0];
1027
+ if (m && m.type === "M") first = { x: m.x, y: m.y };
1028
+ } catch (e3) {
1029
+ }
1030
+ return { id: p.id, len: Math.max(1e-6, motionPathLength(p, _optionalChain([at, 'optionalAccess', _28 => _28.parentWorld]))), first };
1031
+ });
1032
+ if (!list.length) return null;
1033
+ const span = Math.max(0, e - s);
1034
+ if (order2 === "sequence") {
1035
+ if (o.sortBy === "distance") {
1036
+ const b = entry.bounds;
1037
+ const origin = _nullishCoalesce(o.origin, () => ( (b ? { x: b.x + b.width / 2, y: b.y + b.height / 2 } : { x: 0, y: 0 })));
1038
+ list = [...list].sort((a, c) => Math.hypot(a.first.x - origin.x, a.first.y - origin.y) - Math.hypot(c.first.x - origin.x, c.first.y - origin.y));
1039
+ }
1040
+ const total = list.reduce((sum, p) => sum + p.len, 0);
1041
+ let at = s;
1042
+ return list.map((p) => {
1043
+ const w = { id: p.id, start: at, end: at + span * p.len / total };
1044
+ at = w.end;
1045
+ return w;
1046
+ });
1047
+ }
1048
+ const longest = Math.max(...list.map((p) => p.len));
1049
+ return list.map((p) => {
1050
+ const d = span * p.len / longest;
1051
+ return o.align === "end" ? { id: p.id, start: e - d, end: e } : { id: p.id, start: s, end: s + d };
1052
+ });
1053
+ };
1006
1054
  switch (command2.type) {
1007
1055
  case "enter": {
1008
1056
  const style = _nullishCoalesce(command2.style, () => ( "fade"));
1009
1057
  const opacity = rest("opacity");
1010
1058
  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;
1011
- for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _27 => _27.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
1059
+ for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _29 => _29.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
1012
1060
  if (reveal) {
1013
1061
  seg("opacity", opacity, "linear", start, start);
1014
1062
  seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
1015
1063
  break;
1016
1064
  }
1017
1065
  if (style === "draw") {
1018
- seg("drawEnd", 1, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), start, start + duration2 * 0.75, { from: 0 });
1066
+ const windows = drawWindows(start, start + duration2 * 0.75, command2);
1067
+ if (windows) for (const w of windows) push({ node: w.id, property: "drawEnd", start: w.start, end: w.end, from: 0, to: 1, ease: _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), ...where });
1068
+ else seg("drawEnd", 1, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), start, start + duration2 * 0.75, { from: 0 });
1019
1069
  seg("fillOpacity", 1, MOTION_EASE_OUT, start + duration2 * 0.6, end, { from: 0 });
1020
1070
  break;
1021
1071
  }
@@ -1049,7 +1099,9 @@ function compileMotionScene(scene) {
1049
1099
  if (wipe) seg(wipe.property, wipe.to, _nullishCoalesce(ease, () => ( "ease-in")), start, end, { from: wipe.from });
1050
1100
  else if (style === "undraw") {
1051
1101
  seg("fillOpacity", 0, "ease-in", start, start + duration2 * 0.4);
1052
- seg("drawEnd", 0, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)));
1102
+ const windows = drawWindows(start, end, command2);
1103
+ if (windows) for (const w of windows) push({ node: w.id, property: "drawEnd", start: w.start, end: w.end, from: valueAt(w.id, "drawEnd", w.start), to: 0, ease: _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), ...where });
1104
+ else seg("drawEnd", 0, _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)));
1053
1105
  } else if (style === "shrink") {
1054
1106
  seg("scaleX", 0, _nullishCoalesce(ease, () => ( "ease-in")));
1055
1107
  seg("scaleY", 0, _nullishCoalesce(ease, () => ( "ease-in")));
@@ -1064,21 +1116,31 @@ function compileMotionScene(scene) {
1064
1116
  break;
1065
1117
  }
1066
1118
  case "draw": {
1067
- const drawn = tracks.has(motionTrackKey(node, "drawEnd"));
1068
- seg("drawEnd", _nullishCoalesce(command2.to, () => ( 1)), _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT)), start, end, command2.from !== void 0 ? { from: command2.from } : drawn ? {} : { from: 0 });
1119
+ const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
1120
+ const comet = command2.comet !== void 0 ? Math.min(1, Math.max(0, command2.comet)) : void 0;
1121
+ const windows = _nullishCoalesce(drawWindows(start, end, command2), () => ( [{ id: node, start, end }]));
1122
+ for (const w of windows) {
1123
+ if (comet !== void 0) {
1124
+ push({ node: w.id, property: "drawEnd", start: w.start, end: w.end, from: 0, to: 1 + comet, ease: e, ...where });
1125
+ push({ node: w.id, property: "drawStart", start: w.start, end: w.end, from: -comet, to: 1, ease: e, ...where });
1126
+ continue;
1127
+ }
1128
+ const drawn = tracks.has(motionTrackKey(w.id, "drawEnd"));
1129
+ push({ node: w.id, property: "drawEnd", start: w.start, end: w.end, from: _nullishCoalesce(command2.from, () => ( (drawn ? valueAt(w.id, "drawEnd", w.start) : 0))), to: _nullishCoalesce(command2.to, () => ( 1)), ease: e, ...where });
1130
+ }
1069
1131
  break;
1070
1132
  }
1071
1133
  case "move": {
1072
1134
  const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
1073
- let dx = _nullishCoalesce(_optionalChain([command2, 'access', _28 => _28.by, 'optionalAccess', _29 => _29.x]), () => ( 0));
1074
- let dy = _nullishCoalesce(_optionalChain([command2, 'access', _30 => _30.by, 'optionalAccess', _31 => _31.y]), () => ( 0));
1135
+ let dx = _nullishCoalesce(_optionalChain([command2, 'access', _30 => _30.by, 'optionalAccess', _31 => _31.x]), () => ( 0));
1136
+ let dy = _nullishCoalesce(_optionalChain([command2, 'access', _32 => _32.by, 'optionalAccess', _33 => _33.y]), () => ( 0));
1075
1137
  if (command2.to) {
1076
1138
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
1077
1139
  const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
1078
1140
  dx = target.x - (entry.center.x + num("x"));
1079
1141
  dy = target.y - (entry.center.y + num("y"));
1080
1142
  }
1081
- if (_optionalChain([command2, 'access', _32 => _32.via, 'optionalAccess', _33 => _33.length])) {
1143
+ if (_optionalChain([command2, 'access', _34 => _34.via, 'optionalAccess', _35 => _35.length])) {
1082
1144
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
1083
1145
  const x0 = num("x");
1084
1146
  const y0 = num("y");
@@ -1189,7 +1251,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
1189
1251
  var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
1190
1252
  var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
1191
1253
  function applyMotionControlValues(scene, values) {
1192
- if (!values || !_optionalChain([scene, 'access', _34 => _34.controls, 'optionalAccess', _35 => _35.length])) return scene;
1254
+ if (!values || !_optionalChain([scene, 'access', _36 => _36.controls, 'optionalAccess', _37 => _37.length])) return scene;
1193
1255
  const map = /* @__PURE__ */ new Map();
1194
1256
  for (const control of scene.controls) {
1195
1257
  const value = values[control.name];
@@ -1235,7 +1297,7 @@ function shapeIn(entry, shape) {
1235
1297
  }
1236
1298
  function shapeAt(entry, timeline, nodes, time) {
1237
1299
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
1238
- if (!_optionalChain([track, 'optionalAccess', _36 => _36.length])) return null;
1300
+ if (!_optionalChain([track, 'optionalAccess', _38 => _38.length])) return null;
1239
1301
  const s = activeSegment(track, time);
1240
1302
  const fromId = String(s ? s.from : track[0].from);
1241
1303
  if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
@@ -1252,14 +1314,14 @@ function motionFollowRoute(entry, path) {
1252
1314
  if (!path || path.node.type !== "path") return null;
1253
1315
  try {
1254
1316
  return _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path.node, path.parentWorld, { effects: false }), _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld));
1255
- } catch (e3) {
1317
+ } catch (e4) {
1256
1318
  return null;
1257
1319
  }
1258
1320
  }
1259
1321
  function followAt(entry, timeline, nodes, time) {
1260
1322
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
1261
- const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _37 => _37[0]])));
1262
- if (!_optionalChain([s, 'optionalAccess', _38 => _38.path])) return null;
1323
+ const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _39 => _39[0]])));
1324
+ if (!_optionalChain([s, 'optionalAccess', _40 => _40.path])) return null;
1263
1325
  const route = motionFollowRoute(entry, nodes.get(s.path));
1264
1326
  const progress = Number(trackValue(track, time));
1265
1327
  if (!route || !(progress >= 0)) return null;
@@ -1267,7 +1329,7 @@ function followAt(entry, timeline, nodes, time) {
1267
1329
  const at = _chunkO2UH7LZEcjs.bezierPathPointAtLength.call(void 0, route, Math.min(1, Math.max(0, progress)) * _chunkO2UH7LZEcjs.bezierPathLength.call(void 0, route));
1268
1330
  if (!at) return null;
1269
1331
  return { x: at.point[0], y: at.point[1], angle: s.rotate ? at.angle * 180 / Math.PI : 0 };
1270
- } catch (e4) {
1332
+ } catch (e5) {
1271
1333
  return null;
1272
1334
  }
1273
1335
  }
@@ -1319,7 +1381,7 @@ function motionFrameAt(scene, time, options = {}) {
1319
1381
  if (out.visible === void 0) delete out.visible;
1320
1382
  }
1321
1383
  const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
1322
- if (effectTracks.length && _optionalChain([out, 'access', _39 => _39.effects, 'optionalAccess', _40 => _40.length])) {
1384
+ if (effectTracks.length && _optionalChain([out, 'access', _41 => _41.effects, 'optionalAccess', _42 => _42.length])) {
1323
1385
  out = {
1324
1386
  ...out,
1325
1387
  effects: out.effects.map((effect) => {
@@ -1432,7 +1494,7 @@ function motionPath(scene, id, options = {}) {
1432
1494
  const r = (n) => Math.round(n * 100) / 100;
1433
1495
  const centreAt = (t) => {
1434
1496
  const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
1435
- const b = _optionalChain([node, 'optionalAccess', _41 => _41.bounds]);
1497
+ const b = _optionalChain([node, 'optionalAccess', _43 => _43.bounds]);
1436
1498
  return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
1437
1499
  };
1438
1500
  const points = [];
@@ -1472,14 +1534,38 @@ function motionStageSvg(scene, time, options = {}) {
1472
1534
  const open = current.indexOf(">") + 1;
1473
1535
  const close = current.lastIndexOf("</svg>");
1474
1536
  const body = current.slice(open, close);
1475
- const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _42 => _42.exec, 'call', _43 => _43(body), 'optionalAccess', _44 => _44[0]]), () => ( "")) : "";
1537
+ const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _44 => _44.exec, 'call', _45 => _45(body), 'optionalAccess', _46 => _46[0]]), () => ( "")) : "";
1476
1538
  return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
1477
1539
  }
1540
+ function motionContactSheetSvg(scene, times, options = {}) {
1541
+ const { columns, labels = true, ...frameOptions } = options;
1542
+ const n = Math.max(1, times.length);
1543
+ const cols = Math.max(1, Math.min(n, Math.floor(_nullishCoalesce(columns, () => ( Math.ceil(Math.sqrt(n)))))));
1544
+ const rows = Math.ceil(n / cols);
1545
+ const { width: w, height: h } = scene.artwork;
1546
+ const gap = Math.round(Math.max(w, h) * 0.04);
1547
+ const label = labels ? Math.round(Math.max(12, h * 0.08)) : 0;
1548
+ const timeline = compileMotionScene(scene);
1549
+ const W = cols * w + (cols + 1) * gap;
1550
+ const H = rows * (h + label) + (rows + 1) * gap;
1551
+ const prefix = _nullishCoalesce(frameOptions.idPrefix, () => ( "vg-"));
1552
+ const cells = times.map((t, i) => {
1553
+ const x = gap + i % cols * (w + gap);
1554
+ const y = gap + Math.floor(i / cols) * (h + label + gap);
1555
+ const frame = motionStageSvg(scene, t, { ...frameOptions, idPrefix: `${prefix}f${i}-` });
1556
+ const view = _nullishCoalesce(_optionalChain([/viewBox="([^"]*)"/, 'access', _47 => _47.exec, 'call', _48 => _48(frame), 'optionalAccess', _49 => _49[1]]), () => ( `0 0 ${w} ${h}`));
1557
+ const inner = innerSvg(frame).replace(new RegExp(` id="(?!${`${prefix}f${i}-`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
1558
+ const shot = _nullishCoalesce(timeline.shots.find((s) => t >= s.start && t < s.end), () => ( timeline.shots[timeline.shots.length - 1]));
1559
+ const caption = labels ? `<text x="${x + w / 2}" y="${y + h + label * 0.8}" font-family="system-ui, sans-serif" font-size="${Math.round(label * 0.7)}" text-anchor="middle" fill="#64748b">${(t / 1e3).toFixed(2)} s${shot ? ` \xB7 ${shot.id.replace(/[<&]/g, "")}` : ""}</text>` : "";
1560
+ return `<svg x="${x}" y="${y}" width="${w}" height="${h}" viewBox="${view}" overflow="hidden">${inner}</svg><rect x="${x}" y="${y}" width="${w}" height="${h}" fill="none" stroke="#cbd5e1"/>${caption}`;
1561
+ });
1562
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}"><rect width="${W}" height="${H}" fill="#f8fafc"/>${cells.join("")}</svg>`;
1563
+ }
1478
1564
 
1479
1565
  // src/animation-editor/check.ts
1480
1566
  var MOTION_MIN_CHANGE_MS = 120;
1481
1567
  var NUMERIC_PARAMS = (node, effectId) => {
1482
- const effect = _optionalChain([node, 'access', _45 => _45.effects, 'optionalAccess', _46 => _46.find, 'call', _47 => _47((e) => e.id === effectId)]);
1568
+ const effect = _optionalChain([node, 'access', _50 => _50.effects, 'optionalAccess', _51 => _51.find, 'call', _52 => _52((e) => e.id === effectId)]);
1483
1569
  if (!effect) return null;
1484
1570
  return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
1485
1571
  };
@@ -1510,7 +1596,11 @@ function visibleAt(timeline, id, time, node) {
1510
1596
  const circle = clip("clipCircle");
1511
1597
  if (Array.isArray(circle) && circle[2] <= 1e-4) return false;
1512
1598
  const hasFill = node.type === "path" ? !!node.fill && node.fill !== "none" : _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, node).some((p) => p.fill && p.fill !== "none");
1513
- return num("drawEnd", 1) > 0.01 || hasFill && num("fillOpacity", 1) > 0.01;
1599
+ const drawn = num("drawEnd", 1) > 0.01 && (node.type === "path" || _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, node).length === 0 || _chunkYJLCBA7Vcjs.collectPathNodes.call(void 0, node).some((p) => {
1600
+ const v = trackValue(timeline.tracks.get(motionTrackKey(p.id, "drawEnd")), time);
1601
+ return typeof v !== "number" || v > 0.01;
1602
+ }));
1603
+ return drawn || hasFill && num("fillOpacity", 1) > 0.01;
1514
1604
  }
1515
1605
  function checkMotionScene(scene) {
1516
1606
  const issues = [];
@@ -1556,7 +1646,7 @@ function checkMotionScene(scene) {
1556
1646
  if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
1557
1647
  issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
1558
1648
  if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
1559
- if (command2.type === "play" && !_optionalChain([scene, 'access', _48 => _48.clips, 'optionalAccess', _49 => _49[command2.clip]]))
1649
+ if (command2.type === "play" && !_optionalChain([scene, 'access', _53 => _53.clips, 'optionalAccess', _54 => _54[command2.clip]]))
1560
1650
  issues.push(motionIssue("unknown-clip", `There is no clip ${namedWithSuggestion(command2.clip, Object.keys(_nullishCoalesce(scene.clips, () => ( {}))))}; define it with set_animation_clip.`, where));
1561
1651
  if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
1562
1652
  const into = nodes.get(command2.into);
@@ -1649,7 +1739,7 @@ function checkMotionScene(scene) {
1649
1739
  if (!shot.at) return;
1650
1740
  const timing = timeline.shots[i];
1651
1741
  if (timing.cue === void 0) {
1652
- 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', _50 => _50.narration, 'optionalAccess', _51 => _51.words, 'access', _52 => _52.length]), () => ( 0))} words (indexes from 0)`;
1742
+ 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', _55 => _55.narration, 'optionalAccess', _56 => _56.words, 'access', _57 => _57.length]), () => ( 0))} words (indexes from 0)`;
1653
1743
  issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
1654
1744
  } else if (timing.missedBy !== void 0) {
1655
1745
  const late = Math.round(timing.missedBy);
@@ -1682,7 +1772,7 @@ function checkMotionScene(scene) {
1682
1772
  const cam = frame.camera;
1683
1773
  const docIndex = indexMotionNodes(frame.document.nodes);
1684
1774
  for (const id of command2.targets) {
1685
- const b = _optionalChain([docIndex, 'access', _53 => _53.get, 'call', _54 => _54(id), 'optionalAccess', _55 => _55.bounds]);
1775
+ const b = _optionalChain([docIndex, 'access', _58 => _58.get, 'call', _59 => _59(id), 'optionalAccess', _60 => _60.bounds]);
1686
1776
  if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
1687
1777
  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;
1688
1778
  if (outside) {
@@ -1906,9 +1996,9 @@ function applyMotionEdits(scene, edits) {
1906
1996
  let next = scene;
1907
1997
  for (let i = 0; i < list.length; i++) {
1908
1998
  const edit = list[i];
1909
- const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _56 => _56.op])}): ` : "";
1999
+ const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _61 => _61.op])}): ` : "";
1910
2000
  if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
1911
- return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _57 => _57.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
2001
+ return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _62 => _62.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1912
2002
  try {
1913
2003
  next = applyOne(next, edit);
1914
2004
  } catch (error) {
@@ -1924,6 +2014,56 @@ function applyMotionEdits(scene, edits) {
1924
2014
  if (added.length) return { ok: false, error: `Nothing was changed: ${added.map(motionIssueLine).join(" ")}` };
1925
2015
  return { ok: true, scene: next, issues, hints: editHints(before, issues) };
1926
2016
  }
2017
+ var REF = /^\$(\d+|last)((?:\.[A-Za-z_]\w*|\[\d+\])+)$/;
2018
+ function resolveMotionEditRefs(scene, edits) {
2019
+ const outcomes = [];
2020
+ const resolved = [];
2021
+ let next = scene;
2022
+ for (let i = 0; i < edits.length; i++) {
2023
+ const where = `op ${i + 1} (${_optionalChain([edits, 'access', _63 => _63[i], 'optionalAccess', _64 => _64.op])})`;
2024
+ let problem = null;
2025
+ const walk = (v) => {
2026
+ if (typeof v === "string") {
2027
+ if (v.startsWith("$$")) return v.slice(1);
2028
+ const m = REF.exec(v);
2029
+ if (!m) return v;
2030
+ const n = m[1] === "last" ? i - 1 : Number(m[1]);
2031
+ if (!(n >= 0 && n < i)) {
2032
+ problem = `${where}: "${v}" refers to op ${n + 1}, but only ops before this one (1 to ${i}) can be referred to.`;
2033
+ return v;
2034
+ }
2035
+ let value = outcomes[n];
2036
+ for (const part of _nullishCoalesce(m[2].match(/\.[A-Za-z_]\w*|\[\d+\]/g), () => ( []))) value = value == null ? void 0 : value[part.startsWith(".") ? part.slice(1) : part.slice(1, -1)];
2037
+ if (value === void 0) problem = `${where}: "${v}" has no value; op ${n + 1} made ${JSON.stringify(outcomes[n])}.`;
2038
+ return value;
2039
+ }
2040
+ if (Array.isArray(v)) return v.map(walk);
2041
+ if (v && typeof v === "object") return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, walk(x)]));
2042
+ return v;
2043
+ };
2044
+ const edit = walk(edits[i]);
2045
+ if (problem) return { ok: false, error: `${problem} Nothing was changed.` };
2046
+ const before = next;
2047
+ try {
2048
+ next = applyOne(next, edit);
2049
+ } catch (error) {
2050
+ if (error instanceof EditError) return { ok: false, error: `${where}: ${error.message} Nothing was changed.` };
2051
+ throw error;
2052
+ }
2053
+ resolved.push(edit);
2054
+ const out = { op: edit.op };
2055
+ if (edit.op === "insert_shots") out.shots = edit.shots.map((s) => s.id);
2056
+ if ("shot" in edit && typeof edit.shot === "string") out.shot = edit.op === "update_shot" && _optionalChain([edit, 'access', _65 => _65.set, 'optionalAccess', _66 => _66.id]) ? edit.set.id : edit.shot;
2057
+ if (edit.op === "add_commands") {
2058
+ const at = _nullishCoalesce(_nullishCoalesce(edit.at, () => ( _optionalChain([before, 'access', _67 => _67.shots, 'access', _68 => _68.find, 'call', _69 => _69((s) => s.id === edit.shot), 'optionalAccess', _70 => _70.run, 'access', _71 => _71.length]))), () => ( 0));
2059
+ out.indexes = edit.commands.map((_, k) => at + k);
2060
+ }
2061
+ if (edit.op === "move_command") out.indexes = [edit.to];
2062
+ if (edit.op === "set_clip") out.clip = edit.name;
2063
+ outcomes.push(out);
2064
+ }
2065
+ return { ok: true, edits: resolved, outcomes };
2066
+ }
1927
2067
  function createMotionScene(artwork, init = {}) {
1928
2068
  return { version: 1, name: _nullishCoalesce(init.name, () => ( "animation")), ...init, artwork, shots: _nullishCoalesce(init.shots, () => ( [])) };
1929
2069
  }
@@ -2068,7 +2208,7 @@ function toFlairSvgMotion(scene, options = {}) {
2068
2208
  const wrapper = freshId(`${id}--follow`);
2069
2209
  const centre = freshId(`${id}--follow-centre`);
2070
2210
  const c = entry.center;
2071
- const at = _optionalChain([motionNodeOffset, 'call', _58 => _58(entry, timeline, 0, nodes), 'optionalAccess', _59 => _59.follow]);
2211
+ const at = _optionalChain([motionNodeOffset, 'call', _72 => _72(entry, timeline, 0, nodes), 'optionalAccess', _73 => _73.follow]);
2072
2212
  const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
2073
2213
  (wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
2074
2214
  follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
@@ -2128,7 +2268,7 @@ function toFlairSvgMotion(scene, options = {}) {
2128
2268
  exact: () => false,
2129
2269
  value: (t) => {
2130
2270
  const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
2131
- const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _60 => _60(frame.document.nodes), 'access', _61 => _61.get, 'call', _62 => _62(id), 'optionalAccess', _63 => _63.node]));
2271
+ const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _74 => _74(frame.document.nodes), 'access', _75 => _75.get, 'call', _76 => _76(id), 'optionalAccess', _77 => _77.node]));
2132
2272
  if (!now || now.type !== "path") return "";
2133
2273
  const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
2134
2274
  return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
@@ -2149,8 +2289,8 @@ function toFlairSvgMotion(scene, options = {}) {
2149
2289
  ...delay > 0 ? { delay: round2(delay) } : {},
2150
2290
  ms: round2(ms),
2151
2291
  ease: written,
2152
- ..._optionalChain([segment, 'optionalAccess', _64 => _64.behavior]) ? { behavior: segment.behavior } : {},
2153
- ..._optionalChain([segment, 'optionalAccess', _65 => _65.steps]) ? { strobe: segment.steps } : {}
2292
+ ..._optionalChain([segment, 'optionalAccess', _78 => _78.behavior]) ? { behavior: segment.behavior } : {},
2293
+ ..._optionalChain([segment, 'optionalAccess', _79 => _79.steps]) ? { strobe: segment.steps } : {}
2154
2294
  };
2155
2295
  if (channel.kind === "view") animate.push({ ...base, view: to });
2156
2296
  else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
@@ -2239,7 +2379,7 @@ function toFlairSvgMotion(scene, options = {}) {
2239
2379
  let svg = _chunkYJLCBA7Vcjs.vectorDocumentToSvg.call(void 0, { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) }, { pretty: false, precision, wrap });
2240
2380
  if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
2241
2381
  let motion = { svg, animate };
2242
- if (_optionalChain([scene, 'access', _66 => _66.controls, 'optionalAccess', _67 => _67.length])) {
2382
+ if (_optionalChain([scene, 'access', _80 => _80.controls, 'optionalAccess', _81 => _81.length])) {
2243
2383
  const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
2244
2384
  const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
2245
2385
  svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
@@ -2261,7 +2401,7 @@ function toFlairVectorEffect(scene, options = {}) {
2261
2401
  tags: _nullishCoalesce(options.tags, () => ( [])),
2262
2402
  motion: out.motion,
2263
2403
  hold: out.hold,
2264
- ..._optionalChain([scene, 'access', _68 => _68.presets, 'optionalAccess', _69 => _69.length]) ? { presets: scene.presets } : {},
2404
+ ..._optionalChain([scene, 'access', _82 => _82.presets, 'optionalAccess', _83 => _83.length]) ? { presets: scene.presets } : {},
2265
2405
  ...out.trigger ? { trigger: out.trigger } : {}
2266
2406
  };
2267
2407
  }
@@ -2305,7 +2445,7 @@ function resolvePlaceholders(motion, notes) {
2305
2445
  const filled = text.replace(PLACEHOLDER, (_, name) => {
2306
2446
  const d = declared.get(name);
2307
2447
  if (!d) unknown.add(name);
2308
- return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _70 => _70.value]), () => ( ""))).replace(/"/g, '\\"');
2448
+ return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _84 => _84.value]), () => ( ""))).replace(/"/g, '\\"');
2309
2449
  });
2310
2450
  for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
2311
2451
  return { motion: JSON.parse(filled), controls };
@@ -2359,7 +2499,7 @@ function commandsFor(anim, nodes, entered, notes) {
2359
2499
  if (anim.along) {
2360
2500
  const path = nodes.get(anim.along);
2361
2501
  if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
2362
- else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _71 => _71.at, 'optionalAccess', _72 => _72[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _73 => _73.at, 'optionalAccess', _74 => _74[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2502
+ else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _85 => _85.at, 'optionalAccess', _86 => _86[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _87 => _87.at, 'optionalAccess', _88 => _88[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2363
2503
  }
2364
2504
  for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
2365
2505
  if (attr === "opacity") {
@@ -2370,7 +2510,7 @@ function commandsFor(anim, nodes, entered, notes) {
2370
2510
  out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
2371
2511
  } else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
2372
2512
  } else if (attr === "fill" || attr === "stroke") {
2373
- if (_optionalChain([behavior, 'optionalAccess', _75 => _75.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2513
+ if (_optionalChain([behavior, 'optionalAccess', _89 => _89.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2374
2514
  else {
2375
2515
  if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
2376
2516
  out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
@@ -2653,7 +2793,7 @@ function toLottie(scene, options = {}) {
2653
2793
  const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
2654
2794
  const geometryAt = (t) => {
2655
2795
  if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2656
- const now = _optionalChain([indexMotionNodes, 'call', _76 => _76(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _77 => _77.get, 'call', _78 => _78(path.id), 'optionalAccess', _79 => _79.node]);
2796
+ const now = _optionalChain([indexMotionNodes, 'call', _90 => _90(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _91 => _91.get, 'call', _92 => _92(path.id), 'optionalAccess', _93 => _93.node]);
2657
2797
  return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2658
2798
  };
2659
2799
  const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
@@ -2681,11 +2821,13 @@ function toLottie(scene, options = {}) {
2681
2821
  const widthFrom = owner(path, "strokeWidth");
2682
2822
  const fadeFrom = owner(path, "fillOpacity");
2683
2823
  const drawFrom = owner(path, "drawEnd");
2684
- if (drawFrom) {
2685
- const draw = track(drawFrom, "drawEnd");
2686
- items.push({ ty: "tm", nm: "Draw on", s: { a: 0, k: 0 }, e: animate(draw, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(draw, t), () => ( 1))))) * 100]), o: { a: 0, k: 0 }, m: 1 });
2824
+ const tailFrom = owner(path, "drawStart");
2825
+ if (drawFrom || tailFrom) {
2826
+ const pct = (track2, fallback) => (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(track2, t), () => ( fallback))))) * 100];
2827
+ const head = drawFrom ? track(drawFrom, "drawEnd") : [];
2828
+ const tail = tailFrom ? track(tailFrom, "drawStart") : [];
2829
+ items.push({ ty: "tm", nm: "Draw on", s: tail.length ? animate(tail, pct(tail, 0)) : { a: 0, k: 0 }, e: head.length ? animate(head, pct(head, 1)) : { a: 0, k: 100 }, o: { a: 0, k: 0 }, m: 1 });
2687
2830
  }
2688
- if (owner(path, "drawStart")) notes.push(`"${path.id}": drawing from a start other than 0 is not exported.`);
2689
2831
  const stroke = paintOf(path.stroke, "stroke", path.id);
2690
2832
  if (stroke || strokeFrom) {
2691
2833
  const colour = strokeFrom ? track(strokeFrom, "stroke") : [];
@@ -2699,7 +2841,7 @@ function toLottie(scene, options = {}) {
2699
2841
  lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
2700
2842
  ml: 4
2701
2843
  });
2702
- if (_optionalChain([path, 'access', _80 => _80.strokeDasharray, 'optionalAccess', _81 => _81.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2844
+ if (_optionalChain([path, 'access', _94 => _94.strokeDasharray, 'optionalAccess', _95 => _95.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2703
2845
  if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
2704
2846
  }
2705
2847
  const fill = paintOf(path.fill, "fill", path.id);
@@ -2850,7 +2992,7 @@ var AnimationEditorController = (_class = class {
2850
2992
  time: p.time,
2851
2993
  playing: p.playing,
2852
2994
  loop: p.loop,
2853
- currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _82 => _82(timeline, p.time), 'optionalAccess', _83 => _83.id]), () => ( null)),
2995
+ currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _96 => _96(timeline, p.time), 'optionalAccess', _97 => _97.id]), () => ( null)),
2854
2996
  selection: this.selection,
2855
2997
  readOnly: this.readOnly,
2856
2998
  error: this.error,
@@ -2889,7 +3031,7 @@ var AnimationEditorController = (_class = class {
2889
3031
  this.applying = false;
2890
3032
  }
2891
3033
  this.notify();
2892
- _optionalChain([this, 'access', _84 => _84.onChange, 'optionalCall', _85 => _85(scene)]);
3034
+ _optionalChain([this, 'access', _98 => _98.onChange, 'optionalCall', _99 => _99(scene)]);
2893
3035
  }
2894
3036
  commit(label, next) {
2895
3037
  const before = this.scene;
@@ -3071,11 +3213,19 @@ var BLEND = (fallback) => ({
3071
3213
  enum: [...MOTION_BLENDS],
3072
3214
  description: `"add" makes it a layer over the pose that stacks with other changes (a shake on a slide); "replace" makes it a change the next one takes over from (default ${fallback}).`
3073
3215
  });
3216
+ var DRAW_ORDER = {
3217
+ order: { type: "string", enum: ["together", "sequence"], description: "For drawing a group or several paths: together (default) or sequence (one stroke after another, each for a time in proportion to its length)." },
3218
+ pace: { type: "string", enum: ["time", "speed"], description: "With together: time (default, every stroke takes the whole time) or speed (all at one speed, so short strokes take less)." },
3219
+ align: { type: "string", enum: ["start", "end"], description: "With pace speed: start them together (default) or finish them together." },
3220
+ sortBy: { type: "string", enum: ["document", "distance"], description: "With sequence: stacking order (default) or distance from origin (a radial reveal)." },
3221
+ origin: object({ x: { type: "number", description: "Document units." }, y: { type: "number", description: "Document units." } }, ["x", "y"], "With sortBy distance: where strokes are drawn out from (default the target's centre).")
3222
+ };
3074
3223
  var command = (type, description, extra = {}, required = []) => object({ type: { const: type, description }, ...BASE, ...extra }, ["type", "targets", ...required], description);
3075
3224
  var MOTION_COMMAND_SCHEMA = {
3076
3225
  description: `A command (one of ${MOTION_COMMAND_TYPES.join(", ")}). Commands in a shot play one after another unless "with": "previous".`,
3077
3226
  oneOf: [
3078
3227
  command("enter", "Brings hidden targets in; they are hidden before this shot.", {
3228
+ ...DRAW_ORDER,
3079
3229
  style: {
3080
3230
  type: "string",
3081
3231
  enum: [...MOTION_ENTER_STYLES],
@@ -3088,11 +3238,12 @@ var MOTION_COMMAND_SCHEMA = {
3088
3238
  orientation: { type: "string", enum: [...MOTION_SPLIT_ORIENTATIONS], description: "For split: horizontal (default) opens up and down from a horizontal line through the middle; vertical opens left and right." }
3089
3239
  }),
3090
3240
  command("exit", "Takes targets out.", {
3241
+ ...DRAW_ORDER,
3091
3242
  style: { type: "string", enum: [...MOTION_EXIT_STYLES], description: "fade (default), shrink, slide, undraw, or wipe (hides behind a travelling edge through a clip over the target's box: a wipe in, reversed)." },
3092
3243
  direction: { type: "string", enum: [...MOTION_DIRECTIONS], description: "For slide: direction of travel (default down). For wipe: the way the edge travels (default right, hiding from the left)." },
3093
3244
  distance: { type: "number", description: "For slide: document units (default 40)." }
3094
3245
  }),
3095
- 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).") }),
3246
+ command("draw", "Draws strokes on (or off) to a fraction of their length.", { ...DRAW_ORDER, comet: FRACTION("A travelling stroke this fraction of the length long: it runs along the path and off the end (leave out from and to)."), to: FRACTION("Fraction drawn at the end (default 1)."), from: FRACTION("Fraction drawn at the start (default: what is drawn now).") }),
3096
3247
  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.") }),
3097
3248
  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." } }),
3098
3249
  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"]),
@@ -3225,9 +3376,9 @@ var EDIT_OP = {
3225
3376
  object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
3226
3377
  object({ op: { const: "move_shot", description: "Like move_shot." }, shot: SHOT_ID, to: { type: "integer", minimum: 0, description: "New position (0 = first)." } }, ["op", "shot", "to"]),
3227
3378
  object({ op: { const: "add_commands", description: "Like add_commands." }, 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 shot's run (default: the end)." } }, ["op", "shot", "commands"]),
3228
- object({ op: { const: "update_command", description: "Like update_command." }, shot: SHOT_ID, command: { type: "integer", minimum: 0, description: "Index in the shot's run." }, set: { type: "object", description: "Command fields to replace." }, remove: { type: "array", items: { type: "string" }, description: "Command fields to clear." } }, ["op", "shot", "command"]),
3229
- object({ op: { const: "move_command", description: "Like move_command." }, shot: SHOT_ID, command: { type: "integer", minimum: 0, description: "Index in the shot's run." }, to: { type: "integer", minimum: 0, description: "New index in the run (0 = first)." } }, ["op", "shot", "command", "to"]),
3230
- object({ op: { const: "delete_commands", description: "Like delete_commands." }, shot: SHOT_ID, commands: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 1, description: "Indexes in the shot's run." } }, ["op", "shot", "commands"]),
3379
+ object({ op: { const: "update_command", description: "Like update_command." }, shot: SHOT_ID, command: { anyOf: [{ type: "integer", minimum: 0 }, { type: "string", pattern: "^\\$(\\d+|last)[.\\[]" }], description: `Index in the shot's run, or a reference to an earlier op's outcome such as "$0.indexes[0]".` }, set: { type: "object", description: "Command fields to replace." }, remove: { type: "array", items: { type: "string" }, description: "Command fields to clear." } }, ["op", "shot", "command"]),
3380
+ object({ op: { const: "move_command", description: "Like move_command." }, shot: SHOT_ID, command: { anyOf: [{ type: "integer", minimum: 0 }, { type: "string", pattern: "^\\$(\\d+|last)[.\\[]" }], description: `Index in the shot's run, or a reference to an earlier op's outcome such as "$0.indexes[0]".` }, to: { type: "integer", minimum: 0, description: "New index in the run (0 = first)." } }, ["op", "shot", "command", "to"]),
3381
+ object({ op: { const: "delete_commands", description: "Like delete_commands." }, shot: SHOT_ID, commands: { type: "array", items: { anyOf: [{ type: "integer", minimum: 0 }, { type: "string", pattern: "^\\$(\\d+|last)[.\\[]" }], description: `An index, or a reference to an earlier op's outcome such as "$0.indexes[0]".` }, minItems: 1, description: "Indexes in the shot's run." } }, ["op", "shot", "commands"]),
3231
3382
  object({ op: { const: "set_controls", description: "Like set_controls." }, controls: { type: "array", items: CONTROL, description: "Controls (empty clears)." }, presets: { type: "array", items: PRESET, description: "Presets (empty clears)." } }, ["op", "controls"])
3232
3383
  ]
3233
3384
  };
@@ -3371,7 +3522,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3371
3522
  const renderFrame = {
3372
3523
  name: "render_animation_frame",
3373
3524
  title: "Render a frame",
3374
- description: 'Draws the animation at one moment without changing it, to check it: standalone SVG by default, or a PNG image (format "png") when the host can rasterize. Give a time in ms, or a shot and where in it.',
3525
+ description: 'Draws the animation at one moment (or, with times, several moments as one labelled contact sheet) without changing it, to check it: standalone SVG by default, or a PNG image (format "png") when the host can rasterize. Give a time in ms, or a shot and where in it.',
3375
3526
  inputSchema: object({
3376
3527
  time: MS("Time from the start"),
3377
3528
  shot: { ...SHOT_ID, description: 'Render a moment of this shot instead of "time".' },
@@ -3386,10 +3537,12 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3386
3537
  [],
3387
3538
  "Draw faded frames either side of the moment, to see motion in one picture."
3388
3539
  ),
3540
+ times: { type: "array", items: { type: "number", minimum: 0 }, minItems: 2, maxItems: 16, description: "Several moments (ms) to draw as one contact sheet, each labelled with its time and shot, instead of one frame. Overrides time and shot." },
3541
+ columns: { type: "integer", minimum: 1, maximum: 8, description: "With times: frames a row (default about square)." },
3389
3542
  motion_paths: { anyOf: [{ type: "boolean" }, { type: "array", items: { type: "string", minLength: 1 } }], description: "Draw where nodes travel (dashed, with a dot where each change starts or ends): true for every node whose place changes, or a list of ids." }
3390
3543
  }),
3391
3544
  annotations: { title: "Render a frame", ...READ },
3392
- handler: async ({ time, shot, at = "end", format = "svg", onion_skin, motion_paths }) => {
3545
+ handler: async ({ time, shot, at = "end", format = "svg", onion_skin, motion_paths, times, columns }) => {
3393
3546
  const s = scene();
3394
3547
  let t = _nullishCoalesce(time, () => ( controller.getState().time));
3395
3548
  if (shot !== void 0) {
@@ -3401,7 +3554,18 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3401
3554
  const known = new Set(describeMotionElements(s).map((e) => e.id));
3402
3555
  for (const id of motion_paths) if (!known.has(id)) throw new Error(`There is no element ${namedWithSuggestion(id, known)} to draw a motion path for.`);
3403
3556
  }
3404
- const svg = controller.toSvg(t, { pretty: false, ...onion_skin ? { onionSkin: onion_skin } : {}, ...motion_paths ? { motionPaths: motion_paths } : {} });
3557
+ const overlays = { ...onion_skin ? { onionSkin: onion_skin } : {}, ...motion_paths ? { motionPaths: motion_paths } : {} };
3558
+ if (times) {
3559
+ const sheet = motionContactSheetSvg(s, times, { pretty: false, ...overlays, ...columns ? { columns } : {} });
3560
+ if (format === "png") {
3561
+ if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_animation_frame with format "svg".');
3562
+ const size = /width="(\d+)" height="(\d+)"/.exec(sheet);
3563
+ const image = await options.rasterize(sheet, { width: Number(size[1]), height: Number(size[2]) });
3564
+ return { content: [{ type: "image", data: image.data, mimeType: image.mimeType }] };
3565
+ }
3566
+ return { times, svg: sheet };
3567
+ }
3568
+ const svg = controller.toSvg(t, { pretty: false, ...overlays });
3405
3569
  if (format === "png") {
3406
3570
  if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_animation_frame with format "svg".');
3407
3571
  const image = await options.rasterize(svg, { width: s.artwork.width, height: s.artwork.height });
@@ -3426,9 +3590,9 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3426
3590
  handler: ({ from = 0, to, limit = 200 }) => {
3427
3591
  const s = scene();
3428
3592
  const timeline = compileMotionScene(s);
3429
- const words = _nullishCoalesce(_optionalChain([s, 'access', _86 => _86.narration, 'optionalAccess', _87 => _87.words]), () => ( []));
3593
+ const words = _nullishCoalesce(_optionalChain([s, 'access', _100 => _100.narration, 'optionalAccess', _101 => _101.words]), () => ( []));
3430
3594
  const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
3431
- const shotAt = (ms) => _optionalChain([timeline, 'access', _88 => _88.shots, 'access', _89 => _89.find, 'call', _90 => _90((x) => ms >= x.start && ms < x.end), 'optionalAccess', _91 => _91.id]);
3595
+ const shotAt = (ms) => _optionalChain([timeline, 'access', _102 => _102.shots, 'access', _103 => _103.find, 'call', _104 => _104((x) => ms >= x.start && ms < x.end), 'optionalAccess', _105 => _105.id]);
3432
3596
  const beats = [];
3433
3597
  if (s.beat && s.beat.bpm > 0) {
3434
3598
  const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
@@ -3678,7 +3842,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3678
3842
  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"]),
3679
3843
  annotations: { title: "Add commands to a shot", ...WRITE },
3680
3844
  handler: ({ shot, commands, at }) => {
3681
- const before = _nullishCoalesce(_optionalChain([scene, 'call', _92 => _92(), 'access', _93 => _93.shots, 'access', _94 => _94.find, 'call', _95 => _95((s) => s.id === shot), 'optionalAccess', _96 => _96.run, 'access', _97 => _97.length]), () => ( 0));
3845
+ const before = _nullishCoalesce(_optionalChain([scene, 'call', _106 => _106(), 'access', _107 => _107.shots, 'access', _108 => _108.find, 'call', _109 => _109((s) => s.id === shot), 'optionalAccess', _110 => _110.run, 'access', _111 => _111.length]), () => ( 0));
3682
3846
  const first = _nullishCoalesce(at, () => ( before));
3683
3847
  return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
3684
3848
  const shots = describeMotionShots(s);
@@ -3745,12 +3909,14 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3745
3909
  const editAnimation = {
3746
3910
  name: "edit_animation",
3747
3911
  title: "Make several edits at once",
3748
- description: 'Applies several edits in order as one undo step, all or nothing: if one fails, nothing changes and the error names the op ("op 2 (add_commands): \u2026"). With dry_run it only reports the timing and issues the edits would give.',
3912
+ description: `Applies several edits in order as one undo step, all or nothing: if one fails, nothing changes and the error names the op ("op 2 (add_commands): \u2026"). A later op can use what an earlier one made: "$0.indexes[0]" is the index of the first command op 1 added, "$last.shot" the shot the op before changed. Returns each op's outcome (shot, shots, indexes, clip), the timing and issues. With dry_run it only reports them.`,
3749
3913
  inputSchema: object({ ops: { type: "array", items: EDIT_OP, minItems: 1, description: "Edits, applied in order." }, dry_run: { type: "boolean", description: "Check without applying (default false)." } }, ["ops"]),
3750
3914
  annotations: { title: "Make several edits at once", ...WRITE },
3751
3915
  handler: ({ ops, dry_run = false }) => {
3752
- const result = controller.apply(ops, "Edit animation", { dryRun: dry_run });
3753
- return outcome(result, dry_run ? { dry_run: true, hint: "Nothing was applied; call again without dry_run to apply." } : {});
3916
+ const refs = resolveMotionEditRefs(controller.getScene(), ops);
3917
+ if (!refs.ok) throw new Error(refs.error);
3918
+ const result = controller.apply(refs.edits, "Edit animation", { dryRun: dry_run });
3919
+ return outcome(result, { outcomes: refs.outcomes, ...dry_run ? { dry_run: true, hint: "Nothing was applied; call again without dry_run to apply." } : {} });
3754
3920
  }
3755
3921
  };
3756
3922
  const writeAnimation = {
@@ -3840,6 +4006,7 @@ Commands
3840
4006
  - enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.
3841
4007
  - 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.
3842
4008
  - 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.
4009
+ - Drawing a group of strokes: "order": "sequence" draws them one after another (a signature, a diagram), "sortBy": "distance" from "origin" reveals outward, "pace": "speed" keeps one speed; draw with "comet": 0.2 sends a short stroke along a path and off the end.
3843
4010
  - Reuse motion: set_animation_clip defines a clip (commands targeting "$"), and { "type": "play", "clip": name, "targets": [...] } plays it on any nodes (with stagger and speed). copy_animation gives other nodes one node's motion.
3844
4011
  - 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.
3845
4012
  - enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
@@ -3856,7 +4023,8 @@ Commands
3856
4023
  Working method
3857
4024
  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).
3858
4025
  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).
3859
- 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.
4026
+ - In edit_animation, a later op can use what an earlier one made: "$0.indexes[0]" (the first command op 1 added), "$last.shot".
4027
+ 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, or with "times" for a contact sheet of several moments; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.
3860
4028
  - 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.
3861
4029
  4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
3862
4030
 
@@ -3867,7 +4035,7 @@ var TIMING = /* @__PURE__ */ new Set(["duration", "delay", "stagger", "with", "e
3867
4035
  var COLORS = /* @__PURE__ */ new Set(["fill", "stroke", "color"]);
3868
4036
  var NODES = /* @__PURE__ */ new Set(["into", "path"]);
3869
4037
  function branch(type) {
3870
- return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _98 => _98.properties, 'optionalAccess', _99 => _99.type, 'optionalAccess', _100 => _100.const]) === type);
4038
+ return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _112 => _112.properties, 'optionalAccess', _113 => _113.type, 'optionalAccess', _114 => _114.const]) === type);
3871
4039
  }
3872
4040
  function unitOf(type, name) {
3873
4041
  if (name === "duration" || name === "delay" || name === "stagger") return "ms";
@@ -3902,7 +4070,7 @@ function kindOf(name, schema) {
3902
4070
  }
3903
4071
  function motionCommandFields(type) {
3904
4072
  const schema = branch(type);
3905
- if (!_optionalChain([schema, 'optionalAccess', _101 => _101.properties])) return [];
4073
+ if (!_optionalChain([schema, 'optionalAccess', _115 => _115.properties])) return [];
3906
4074
  const required = new Set(_nullishCoalesce(schema.required, () => ( [])));
3907
4075
  const fields = Object.entries(schema.properties).filter(([name]) => name !== "type").map(([name, s]) => {
3908
4076
  const kind = kindOf(name, s);
@@ -3959,7 +4127,7 @@ function defaultMotionCommand(type, targets, scene) {
3959
4127
  if (type === "wait") return { type: "wait", duration: 500 };
3960
4128
  if (!targets.length) return { error: "Pick a target first: click an element on the stage." };
3961
4129
  const nodes = [...indexMotionNodes(scene.artwork.nodes).values()];
3962
- const other = (pred) => _optionalChain([nodes, 'access', _102 => _102.find, 'call', _103 => _103((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _104 => _104.node, 'access', _105 => _105.id]);
4130
+ const other = (pred) => _optionalChain([nodes, 'access', _116 => _116.find, 'call', _117 => _117((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _118 => _118.node, 'access', _119 => _119.id]);
3963
4131
  const base = { targets: [...targets] };
3964
4132
  switch (type) {
3965
4133
  case "scale":
@@ -3990,8 +4158,8 @@ function defaultMotionCommand(type, targets, scene) {
3990
4158
  }
3991
4159
  case "effect": {
3992
4160
  for (const id of targets) {
3993
- const node = _optionalChain([nodes, 'access', _106 => _106.find, 'call', _107 => _107((e) => e.node.id === id), 'optionalAccess', _108 => _108.node]);
3994
- for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _109 => _109.effects]), () => ( []))) {
4161
+ const node = _optionalChain([nodes, 'access', _120 => _120.find, 'call', _121 => _121((e) => e.node.id === id), 'optionalAccess', _122 => _122.node]);
4162
+ for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _123 => _123.effects]), () => ( []))) {
3995
4163
  const param = Object.entries(effect.params).find(([, v]) => typeof v === "number");
3996
4164
  if (param) return { type, ...base, effect: effect.id, param: param[0], to: param[1] + 1 };
3997
4165
  }
@@ -4016,11 +4184,11 @@ function motionCommandRows(scene, shotId) {
4016
4184
  return scene.shots[index].run.map((command2, i) => {
4017
4185
  const t = timeline.commands.find((c) => c.shotIndex === index && c.command === i);
4018
4186
  const primitive = motionFlairPrimitive(command2);
4019
- return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _110 => _110.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _111 => _111.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
4187
+ return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _124 => _124.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _125 => _125.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
4020
4188
  });
4021
4189
  }
4022
4190
  function duplicateMotionCommandEdit(scene, shot, command2) {
4023
- const run = _nullishCoalesce(_optionalChain([scene, 'access', _112 => _112.shots, 'access', _113 => _113.find, 'call', _114 => _114((s) => s.id === shot), 'optionalAccess', _115 => _115.run]), () => ( []));
4191
+ const run = _nullishCoalesce(_optionalChain([scene, 'access', _126 => _126.shots, 'access', _127 => _127.find, 'call', _128 => _128((s) => s.id === shot), 'optionalAccess', _129 => _129.run]), () => ( []));
4024
4192
  return { op: "add_commands", shot, commands: run[command2] ? [structuredClone(run[command2])] : [], at: command2 + 1 };
4025
4193
  }
4026
4194
  var MOTION_EASE_CHOICES = [
@@ -4105,7 +4273,7 @@ function motionCueFromKey(key) {
4105
4273
  function motionCueChoices(scene, limit = 256) {
4106
4274
  const choices = [];
4107
4275
  const s = (ms) => `${(ms / 1e3).toFixed(2)} s`;
4108
- const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _116 => _116.narration, 'optionalAccess', _117 => _117.words, 'access', _118 => _118.map, 'call', _119 => _119((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
4276
+ const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _130 => _130.narration, 'optionalAccess', _131 => _131.words, 'access', _132 => _132.map, 'call', _133 => _133((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
4109
4277
  if (scene.beat && scene.beat.bpm > 0) {
4110
4278
  for (let beat = 0; choices.length < limit; beat++) {
4111
4279
  const time = motionBeatTime(scene.beat, beat);
@@ -4115,7 +4283,7 @@ function motionCueChoices(scene, limit = 256) {
4115
4283
  choices.push({ value: `beat:${beat}`, label: `Beat ${beat} (bar ${at.bar}.${at.beatInBar}) \xB7 ${s(time)}`, time });
4116
4284
  }
4117
4285
  }
4118
- _optionalChain([scene, 'access', _120 => _120.narration, 'optionalAccess', _121 => _121.words, 'access', _122 => _122.forEach, 'call', _123 => _123((w, i) => {
4286
+ _optionalChain([scene, 'access', _134 => _134.narration, 'optionalAccess', _135 => _135.words, 'access', _136 => _136.forEach, 'call', _137 => _137((w, i) => {
4119
4287
  if (choices.length < limit * 2) choices.push({ value: `word:${i}`, label: `Word ${i} \u201C${w.text}\u201D \xB7 ${s(w.start)}`, time: w.start });
4120
4288
  })]);
4121
4289
  return choices;
@@ -4124,7 +4292,7 @@ function motionShotCueTime(scene, cue) {
4124
4292
  return cue ? motionCueTime(scene, cue) : void 0;
4125
4293
  }
4126
4294
  function formatMotionNarration(narration) {
4127
- return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _124 => _124.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
4295
+ return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _138 => _138.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
4128
4296
  }
4129
4297
  function parseMotionNarration(text) {
4130
4298
  const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
@@ -4264,5 +4432,7 @@ function parseMotionNarration(text) {
4264
4432
 
4265
4433
 
4266
4434
 
4267
- 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_CLIP_TARGET = MOTION_CLIP_TARGET; 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.clipsShapeProblems = clipsShapeProblems; 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.motionClipLayout = motionClipLayout; 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;
4435
+
4436
+
4437
+ 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_CLIP_TARGET = MOTION_CLIP_TARGET; 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.clipsShapeProblems = clipsShapeProblems; 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.motionClipLayout = motionClipLayout; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionContactSheetSvg = motionContactSheetSvg; 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.resolveMotionEditRefs = resolveMotionEditRefs; 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;
4268
4438
  //# sourceMappingURL=index.cjs.map