@pixerate/editor 0.62.0 → 0.64.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.
@@ -24,6 +24,7 @@ var _chunkYJLCBA7Vcjs = require('../chunk-YJLCBA7V.cjs');
24
24
 
25
25
 
26
26
 
27
+
27
28
  var _chunkO2UH7LZEcjs = require('../chunk-O2UH7LZE.cjs');
28
29
 
29
30
 
@@ -149,6 +150,8 @@ var MOTION_ISSUE_CODES = {
149
150
  "out-of-frame": { severity: "warning", wording: "A node a command animates ends up outside the camera." },
150
151
  "too-fast": { severity: "warning", wording: "A change is too quick to follow." },
151
152
  "spring-cut-short": { severity: "warning", wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end." },
153
+ "unknown-cue": { severity: "error", wording: "A shot is cued to a beat with no beat grid, or to a word the narration does not have." },
154
+ "cue-missed": { severity: "warning", wording: "A shot starts after its cue, because the shot before runs past it." },
152
155
  "scene-too-long": { severity: "warning", wording: "The scene runs past its target length." },
153
156
  "scene-too-short": { severity: "warning", wording: "The scene ends before its target length." },
154
157
  "unknown-control-color": { severity: "warning", wording: "A control stands for a colour the scene never uses." },
@@ -206,8 +209,8 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
206
209
  this.problems = problems;
207
210
  }
208
211
  };
209
- var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "target", "controls", "presets"];
210
- var SHOT_KEYS = ["id", "label", "camera", "run", "hold", "speed"];
212
+ var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
213
+ var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
211
214
  var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
212
215
  var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
213
216
  var COMMAND_KEYS = {
@@ -361,6 +364,16 @@ function shotShapeProblems(shot, where) {
361
364
  if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
362
365
  if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
363
366
  if (shot.speed !== void 0 && !(isNum(shot.speed) && shot.speed >= 0.05 && shot.speed <= 20)) problems.push(`${name}: "speed" must be a number from 0.05 to 20 (2 plays it twice as fast).`);
367
+ if (shot.at !== void 0) {
368
+ const at = shot.at;
369
+ const given = isObject(at) ? ["beat", "word", "ms"].filter((k) => at[k] !== void 0) : [];
370
+ if (!isObject(at) || given.length !== 1) problems.push(`${name}: "at" must give one of { "beat": number }, { "word": index } or { "ms": number }.`);
371
+ else {
372
+ unknownKeys(at, ["beat", "word", "ms"], `${name} at`, problems);
373
+ const k = given[0];
374
+ if (!isNum(at[k]) || at[k] < 0 || k === "word" && !Number.isInteger(at[k])) problems.push(`${name}: "at.${k}" must be ${k === "word" ? "a word index \u2265 0" : "a number \u2265 0"}.`);
375
+ }
376
+ }
364
377
  if (shot.camera !== void 0) {
365
378
  const c = shot.camera;
366
379
  if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
@@ -391,6 +404,16 @@ function motionSceneShapeProblems(input) {
391
404
  problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
392
405
  if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
393
406
  if (input.fps !== void 0 && !(isNum(input.fps) && input.fps > 0 && input.fps <= 120)) problems.push('Scene: "fps" must be a number from 1 to 120.');
407
+ if (input.beat !== void 0) {
408
+ const b = input.beat;
409
+ if (!isObject(b) || !(isNum(b.bpm) && b.bpm >= 1 && b.bpm <= 1e3) || b.offset !== void 0 && !isNum(b.offset) || b.meter !== void 0 && !(Number.isInteger(b.meter) && b.meter >= 1))
410
+ problems.push('Scene: "beat" must be { "bpm": 1\u20131000, "offset"?: ms, "meter"?: beats a bar }.');
411
+ }
412
+ if (input.narration !== void 0) {
413
+ const n = input.narration;
414
+ if (!isObject(n) || !Array.isArray(n.words) || !n.words.every((w, i, all) => isObject(w) && typeof w.text === "string" && isNum(w.start) && w.start >= 0 && (w.end === void 0 || isNum(w.end) && w.end >= w.start) && (i === 0 || w.start >= (_nullishCoalesce(all[i - 1].start, () => ( 0))))))
415
+ problems.push('Scene: "narration" must be { "words": [{ "text", "start": ms, "end"?: ms }, \u2026] } in time order.');
416
+ }
394
417
  if (input.steps !== void 0 && !(isNum(input.steps) && input.steps > 0 && input.steps <= 120)) problems.push('Scene: "steps" must be frames per second from above 0 to 120.');
395
418
  if (input.target !== void 0 && !(Array.isArray(input.target) && input.target.length === 2 && input.target.every(isNum) && input.target[0] <= input.target[1]))
396
419
  problems.push('Scene: "target" must be [min, max] seconds.');
@@ -699,6 +722,21 @@ function motionPathLength(node, parentWorld = _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)
699
722
  function motionShotTimeScale(shot) {
700
723
  return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
701
724
  }
725
+ function motionBeatTime(grid, beat) {
726
+ return (_nullishCoalesce(grid.offset, () => ( 0))) + beat * 6e4 / grid.bpm;
727
+ }
728
+ function motionBeatAt(grid, ms) {
729
+ const beat = (ms - (_nullishCoalesce(grid.offset, () => ( 0)))) * grid.bpm / 6e4;
730
+ const meter = Math.max(1, Math.round(_nullishCoalesce(grid.meter, () => ( 4))));
731
+ const whole = Math.floor(beat + 1e-9);
732
+ return { beat, bar: Math.floor(whole / meter) + 1, beatInBar: (whole % meter + meter) % meter + 1 };
733
+ }
734
+ function motionCueTime(scene, cue) {
735
+ if (typeof cue.ms === "number") return Math.max(0, cue.ms);
736
+ if (typeof cue.beat === "number") return scene.beat && scene.beat.bpm > 0 ? Math.max(0, motionBeatTime(scene.beat, cue.beat)) : void 0;
737
+ if (typeof cue.word === "number") return _optionalChain([scene, 'access', _18 => _18.narration, 'optionalAccess', _19 => _19.words, 'access', _20 => _20[cue.word], 'optionalAccess', _21 => _21.start]);
738
+ return void 0;
739
+ }
702
740
  function motionCommandRepeat(command2) {
703
741
  if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
704
742
  const n = _nullishCoalesce(command2.repeat, () => ( 1));
@@ -736,6 +774,12 @@ function compileMotionScene(scene) {
736
774
  const cameraMoves = [];
737
775
  let t = 0;
738
776
  scene.shots.forEach((shot, index) => {
777
+ const cue = shot.at ? motionCueTime(scene, shot.at) : void 0;
778
+ if (cue !== void 0 && cue > t) {
779
+ if (shots.length) shots[shots.length - 1].end = cue;
780
+ t = cue;
781
+ }
782
+ const missedBy = cue !== void 0 && t - cue > 0.5 ? t - cue : void 0;
739
783
  const start = t;
740
784
  let cursor = start;
741
785
  const f = motionShotTimeScale(shot);
@@ -762,7 +806,7 @@ function compileMotionScene(scene) {
762
806
  runEnd = Math.max(runEnd, end2);
763
807
  });
764
808
  const end = runEnd + (_nullishCoalesce(shot.hold, () => ( MOTION_DEFAULT_HOLD))) * f;
765
- shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end });
809
+ shots.push({ id: shot.id, index, ...shot.label ? { label: shot.label } : {}, start, commandsStart, commandsEnd: runEnd, end, ...cue !== void 0 ? { cue } : {}, ...missedBy !== void 0 ? { missedBy } : {} });
766
810
  t = end;
767
811
  });
768
812
  const tracks = /* @__PURE__ */ new Map();
@@ -842,7 +886,7 @@ function compileMotionScene(scene) {
842
886
  const style = _nullishCoalesce(command2.style, () => ( "fade"));
843
887
  const opacity = rest("opacity");
844
888
  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;
845
- for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _18 => _18.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
889
+ 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);
846
890
  if (reveal) {
847
891
  seg("opacity", opacity, "linear", start, start);
848
892
  seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
@@ -904,8 +948,8 @@ function compileMotionScene(scene) {
904
948
  }
905
949
  case "move": {
906
950
  const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
907
- let dx = _nullishCoalesce(_optionalChain([command2, 'access', _19 => _19.by, 'optionalAccess', _20 => _20.x]), () => ( 0));
908
- let dy = _nullishCoalesce(_optionalChain([command2, 'access', _21 => _21.by, 'optionalAccess', _22 => _22.y]), () => ( 0));
951
+ let dx = _nullishCoalesce(_optionalChain([command2, 'access', _23 => _23.by, 'optionalAccess', _24 => _24.x]), () => ( 0));
952
+ let dy = _nullishCoalesce(_optionalChain([command2, 'access', _25 => _25.by, 'optionalAccess', _26 => _26.y]), () => ( 0));
909
953
  if (command2.to) {
910
954
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
911
955
  const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
@@ -1009,7 +1053,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
1009
1053
  var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
1010
1054
  var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
1011
1055
  function applyMotionControlValues(scene, values) {
1012
- if (!values || !_optionalChain([scene, 'access', _23 => _23.controls, 'optionalAccess', _24 => _24.length])) return scene;
1056
+ if (!values || !_optionalChain([scene, 'access', _27 => _27.controls, 'optionalAccess', _28 => _28.length])) return scene;
1013
1057
  const map = /* @__PURE__ */ new Map();
1014
1058
  for (const control of scene.controls) {
1015
1059
  const value = values[control.name];
@@ -1055,7 +1099,7 @@ function shapeIn(entry, shape) {
1055
1099
  }
1056
1100
  function shapeAt(entry, timeline, nodes, time) {
1057
1101
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
1058
- if (!_optionalChain([track, 'optionalAccess', _25 => _25.length])) return null;
1102
+ if (!_optionalChain([track, 'optionalAccess', _29 => _29.length])) return null;
1059
1103
  const s = activeSegment(track, time);
1060
1104
  const fromId = String(s ? s.from : track[0].from);
1061
1105
  if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
@@ -1078,8 +1122,8 @@ function motionFollowRoute(entry, path) {
1078
1122
  }
1079
1123
  function followAt(entry, timeline, nodes, time) {
1080
1124
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
1081
- const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _26 => _26[0]])));
1082
- if (!_optionalChain([s, 'optionalAccess', _27 => _27.path])) return null;
1125
+ const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _30 => _30[0]])));
1126
+ if (!_optionalChain([s, 'optionalAccess', _31 => _31.path])) return null;
1083
1127
  const route = motionFollowRoute(entry, nodes.get(s.path));
1084
1128
  const progress = Number(trackValue(track, time));
1085
1129
  if (!route || !(progress >= 0)) return null;
@@ -1139,7 +1183,7 @@ function motionFrameAt(scene, time, options = {}) {
1139
1183
  if (out.visible === void 0) delete out.visible;
1140
1184
  }
1141
1185
  const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
1142
- if (effectTracks.length && _optionalChain([out, 'access', _28 => _28.effects, 'optionalAccess', _29 => _29.length])) {
1186
+ if (effectTracks.length && _optionalChain([out, 'access', _32 => _32.effects, 'optionalAccess', _33 => _33.length])) {
1143
1187
  out = {
1144
1188
  ...out,
1145
1189
  effects: out.effects.map((effect) => {
@@ -1217,7 +1261,7 @@ function withViewBox(svg, box) {
1217
1261
  // src/animation-editor/check.ts
1218
1262
  var MOTION_MIN_CHANGE_MS = 120;
1219
1263
  var NUMERIC_PARAMS = (node, effectId) => {
1220
- const effect = _optionalChain([node, 'access', _30 => _30.effects, 'optionalAccess', _31 => _31.find, 'call', _32 => _32((e) => e.id === effectId)]);
1264
+ const effect = _optionalChain([node, 'access', _34 => _34.effects, 'optionalAccess', _35 => _35.find, 'call', _36 => _36((e) => e.id === effectId)]);
1221
1265
  if (!effect) return null;
1222
1266
  return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
1223
1267
  };
@@ -1366,6 +1410,17 @@ function checkMotionScene(scene) {
1366
1410
  }
1367
1411
  });
1368
1412
  });
1413
+ scene.shots.forEach((shot, i) => {
1414
+ if (!shot.at) return;
1415
+ const timing = timeline.shots[i];
1416
+ if (timing.cue === void 0) {
1417
+ 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', _37 => _37.narration, 'optionalAccess', _38 => _38.words, 'access', _39 => _39.length]), () => ( 0))} words (indexes from 0)`;
1418
+ issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
1419
+ } else if (timing.missedBy !== void 0) {
1420
+ const late = Math.round(timing.missedBy);
1421
+ issues.push(motionIssue("cue-missed", `Shot "${shot.id}" is cued at ${Math.round(timing.cue)} ms but starts ${late} ms late, because the shot before runs until ${Math.round(timing.start)} ms; shorten that shot (its hold, durations or speed) or cue this one later.`, { shot: shot.id, measured: late, needed: 0, unit: "ms" }));
1422
+ }
1423
+ });
1369
1424
  for (const [key, track] of timeline.tracks) {
1370
1425
  if (key.endsWith("\0add")) continue;
1371
1426
  for (let i = 1; i < track.length; i++) {
@@ -1392,7 +1447,7 @@ function checkMotionScene(scene) {
1392
1447
  const cam = frame.camera;
1393
1448
  const docIndex = indexMotionNodes(frame.document.nodes);
1394
1449
  for (const id of command2.targets) {
1395
- const b = _optionalChain([docIndex, 'access', _33 => _33.get, 'call', _34 => _34(id), 'optionalAccess', _35 => _35.bounds]);
1450
+ const b = _optionalChain([docIndex, 'access', _40 => _40.get, 'call', _41 => _41(id), 'optionalAccess', _42 => _42.bounds]);
1396
1451
  if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
1397
1452
  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;
1398
1453
  if (outside) {
@@ -1467,6 +1522,14 @@ function applyOne(scene, edit) {
1467
1522
  if (!(typeof edit.steps === "number" && edit.steps > 0 && edit.steps <= 120)) fail('"steps" must be frames per second from above 0 to 120 (12 is "on twos" at 24 fps), or null for smooth.');
1468
1523
  next.steps = edit.steps;
1469
1524
  }
1525
+ for (const key of ["beat", "narration"]) {
1526
+ if (edit[key] === null) delete next[key];
1527
+ else if (edit[key] !== void 0) next[key] = edit[key];
1528
+ }
1529
+ {
1530
+ const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"/.test(p));
1531
+ if (problems.length) fail(problems.join(" "));
1532
+ }
1470
1533
  if (edit.target === null) delete next.target;
1471
1534
  else if (edit.target !== void 0) next.target = edit.target;
1472
1535
  return next;
@@ -1574,9 +1637,9 @@ function applyMotionEdits(scene, edits) {
1574
1637
  let next = scene;
1575
1638
  for (let i = 0; i < list.length; i++) {
1576
1639
  const edit = list[i];
1577
- const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _36 => _36.op])}): ` : "";
1640
+ const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _43 => _43.op])}): ` : "";
1578
1641
  if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
1579
- return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _37 => _37.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1642
+ return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _44 => _44.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1580
1643
  try {
1581
1644
  next = applyOne(next, edit);
1582
1645
  } catch (error) {
@@ -1736,7 +1799,7 @@ function toFlairSvgMotion(scene, options = {}) {
1736
1799
  const wrapper = freshId(`${id}--follow`);
1737
1800
  const centre = freshId(`${id}--follow-centre`);
1738
1801
  const c = entry.center;
1739
- const at = _optionalChain([motionNodeOffset, 'call', _38 => _38(entry, timeline, 0, nodes), 'optionalAccess', _39 => _39.follow]);
1802
+ const at = _optionalChain([motionNodeOffset, 'call', _45 => _45(entry, timeline, 0, nodes), 'optionalAccess', _46 => _46.follow]);
1740
1803
  const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
1741
1804
  (wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
1742
1805
  follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
@@ -1796,7 +1859,7 @@ function toFlairSvgMotion(scene, options = {}) {
1796
1859
  exact: () => false,
1797
1860
  value: (t) => {
1798
1861
  const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
1799
- const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _40 => _40(frame.document.nodes), 'access', _41 => _41.get, 'call', _42 => _42(id), 'optionalAccess', _43 => _43.node]));
1862
+ const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _47 => _47(frame.document.nodes), 'access', _48 => _48.get, 'call', _49 => _49(id), 'optionalAccess', _50 => _50.node]));
1800
1863
  if (!now || now.type !== "path") return "";
1801
1864
  const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
1802
1865
  return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
@@ -1817,8 +1880,8 @@ function toFlairSvgMotion(scene, options = {}) {
1817
1880
  ...delay > 0 ? { delay: round2(delay) } : {},
1818
1881
  ms: round2(ms),
1819
1882
  ease: written,
1820
- ..._optionalChain([segment, 'optionalAccess', _44 => _44.behavior]) ? { behavior: segment.behavior } : {},
1821
- ..._optionalChain([segment, 'optionalAccess', _45 => _45.steps]) ? { strobe: segment.steps } : {}
1883
+ ..._optionalChain([segment, 'optionalAccess', _51 => _51.behavior]) ? { behavior: segment.behavior } : {},
1884
+ ..._optionalChain([segment, 'optionalAccess', _52 => _52.steps]) ? { strobe: segment.steps } : {}
1822
1885
  };
1823
1886
  if (channel.kind === "view") animate.push({ ...base, view: to });
1824
1887
  else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
@@ -1891,7 +1954,7 @@ function toFlairSvgMotion(scene, options = {}) {
1891
1954
  if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
1892
1955
  const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
1893
1956
  let motion = { svg, animate };
1894
- if (_optionalChain([scene, 'access', _46 => _46.controls, 'optionalAccess', _47 => _47.length])) {
1957
+ if (_optionalChain([scene, 'access', _53 => _53.controls, 'optionalAccess', _54 => _54.length])) {
1895
1958
  const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
1896
1959
  const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
1897
1960
  svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
@@ -1913,7 +1976,7 @@ function toFlairVectorEffect(scene, options = {}) {
1913
1976
  tags: _nullishCoalesce(options.tags, () => ( [])),
1914
1977
  motion: out.motion,
1915
1978
  hold: out.hold,
1916
- ..._optionalChain([scene, 'access', _48 => _48.presets, 'optionalAccess', _49 => _49.length]) ? { presets: scene.presets } : {},
1979
+ ..._optionalChain([scene, 'access', _55 => _55.presets, 'optionalAccess', _56 => _56.length]) ? { presets: scene.presets } : {},
1917
1980
  ...options.trigger ? { trigger: options.trigger } : {}
1918
1981
  };
1919
1982
  }
@@ -1956,7 +2019,7 @@ function resolvePlaceholders(motion, notes) {
1956
2019
  const filled = text.replace(PLACEHOLDER, (_, name) => {
1957
2020
  const d = declared.get(name);
1958
2021
  if (!d) unknown.add(name);
1959
- return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _50 => _50.value]), () => ( ""))).replace(/"/g, '\\"');
2022
+ return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _57 => _57.value]), () => ( ""))).replace(/"/g, '\\"');
1960
2023
  });
1961
2024
  for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
1962
2025
  return { motion: JSON.parse(filled), controls };
@@ -2010,7 +2073,7 @@ function commandsFor(anim, nodes, entered, notes) {
2010
2073
  if (anim.along) {
2011
2074
  const path = nodes.get(anim.along);
2012
2075
  if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
2013
- else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _51 => _51.at, 'optionalAccess', _52 => _52[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _53 => _53.at, 'optionalAccess', _54 => _54[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2076
+ else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _58 => _58.at, 'optionalAccess', _59 => _59[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _60 => _60.at, 'optionalAccess', _61 => _61[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2014
2077
  }
2015
2078
  for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
2016
2079
  if (attr === "opacity") {
@@ -2021,7 +2084,7 @@ function commandsFor(anim, nodes, entered, notes) {
2021
2084
  out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
2022
2085
  } else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
2023
2086
  } else if (attr === "fill" || attr === "stroke") {
2024
- if (_optionalChain([behavior, 'optionalAccess', _55 => _55.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2087
+ if (_optionalChain([behavior, 'optionalAccess', _62 => _62.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2025
2088
  else {
2026
2089
  if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
2027
2090
  out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
@@ -2121,6 +2184,311 @@ function fromFlairVectorEffect(effect, options = {}) {
2121
2184
  return { scene, notes, issues: checkMotionScene(scene) };
2122
2185
  }
2123
2186
 
2187
+ // src/animation-editor/lottie.ts
2188
+ var STATIC_TR = () => ({
2189
+ ty: "tr",
2190
+ p: { a: 0, k: [0, 0] },
2191
+ a: { a: 0, k: [0, 0] },
2192
+ s: { a: 0, k: [100, 100] },
2193
+ r: { a: 0, k: 0 },
2194
+ o: { a: 0, k: 100 },
2195
+ sk: { a: 0, k: 0 },
2196
+ sa: { a: 0, k: 0 }
2197
+ });
2198
+ function rgba(color) {
2199
+ const hex = typeof color === "string" ? _chunkA6PHCAGXcjs.normalizeTokenColor.call(void 0, color) : null;
2200
+ if (!hex) return null;
2201
+ const n = (i) => parseInt(hex.slice(i, i + 2), 16) / 255;
2202
+ return [n(1), n(3), n(5), hex.length >= 9 ? parseInt(hex.slice(7, 9), 16) / 255 : 1];
2203
+ }
2204
+ function lottieContours(path, round2) {
2205
+ const out = [];
2206
+ let cur = null;
2207
+ let px = 0;
2208
+ let py = 0;
2209
+ const add = (x, y, inX, inY) => {
2210
+ cur.v.push([x, y]);
2211
+ cur.i.push([inX - x, inY - y]);
2212
+ cur.o.push([0, 0]);
2213
+ px = x;
2214
+ py = y;
2215
+ };
2216
+ for (const cmd of path) {
2217
+ if (cmd.type === "M") {
2218
+ if (cur) out.push(cur);
2219
+ cur = { c: false, v: [], i: [], o: [] };
2220
+ add(cmd.x, cmd.y, cmd.x, cmd.y);
2221
+ } else if (!cur) continue;
2222
+ else if (cmd.type === "L") add(cmd.x, cmd.y, cmd.x, cmd.y);
2223
+ else if (cmd.type === "Q") {
2224
+ const c1 = [px + 2 / 3 * (cmd.x1 - px), py + 2 / 3 * (cmd.y1 - py)];
2225
+ const c2 = [cmd.x + 2 / 3 * (cmd.x1 - cmd.x), cmd.y + 2 / 3 * (cmd.y1 - cmd.y)];
2226
+ cur.o[cur.o.length - 1] = [c1[0] - px, c1[1] - py];
2227
+ add(cmd.x, cmd.y, c2[0], c2[1]);
2228
+ } else if (cmd.type === "C") {
2229
+ cur.o[cur.o.length - 1] = [cmd.x1 - px, cmd.y1 - py];
2230
+ add(cmd.x, cmd.y, cmd.x2, cmd.y2);
2231
+ } else if (cmd.type === "Z") {
2232
+ cur.c = true;
2233
+ const n = cur.v.length;
2234
+ if (n > 1 && Math.abs(cur.v[n - 1][0] - cur.v[0][0]) < 1e-9 && Math.abs(cur.v[n - 1][1] - cur.v[0][1]) < 1e-9) {
2235
+ cur.i[0] = cur.i[n - 1];
2236
+ cur.v.pop();
2237
+ cur.i.pop();
2238
+ cur.o.pop();
2239
+ }
2240
+ out.push(cur);
2241
+ cur = null;
2242
+ }
2243
+ }
2244
+ if (cur) out.push(cur);
2245
+ return out.map((c) => ({ c: c.c, v: c.v.map((p) => p.map(round2)), i: c.i.map((p) => p.map(round2)), o: c.o.map((p) => p.map(round2)) }));
2246
+ }
2247
+ function staticParts(m) {
2248
+ const sx = Math.hypot(m[0], m[1]);
2249
+ if (!(sx > 1e-12)) return null;
2250
+ const sy = (m[0] * m[3] - m[1] * m[2]) / sx;
2251
+ if (Math.abs(m[2] + m[1] / sx * sy) > 1e-6 || Math.abs(m[3] - m[0] / sx * sy) > 1e-6) return null;
2252
+ return { p: [m[4], m[5]], s: [sx * 100, sy * 100], r: Math.atan2(m[1], m[0]) * 180 / Math.PI };
2253
+ }
2254
+ function toLottie(scene, options = {}) {
2255
+ const fps = _nullishCoalesce(_nullishCoalesce(options.fps, () => ( scene.fps)), () => ( 30));
2256
+ const precision = _nullishCoalesce(options.precision, () => ( 3));
2257
+ const round2 = (n) => Math.round(n * 10 ** precision) / 10 ** precision;
2258
+ const frame = (ms) => round2(ms * fps / 1e3);
2259
+ const timeline = compileMotionScene(scene);
2260
+ const nodes = indexMotionNodes(scene.artwork.nodes);
2261
+ const notes = [];
2262
+ let sampled = 0;
2263
+ const track = (id, p) => _nullishCoalesce(timeline.tracks.get(motionTrackKey(id, p)), () => ( []));
2264
+ const layers = (id, p) => _nullishCoalesce(timeline.tracks.get(`${motionTrackKey(id, p)}\0add`), () => ( []));
2265
+ const end = timeline.duration;
2266
+ const curveOf = (s) => {
2267
+ if (s.behavior || s.steps || s.geometric) return void 0;
2268
+ const e = s.reverse ? _chunk2FHLHZALcjs.reverseCubicBezier.call(void 0, s.ease) : _chunk2FHLHZALcjs.resolveEasing.call(void 0, s.ease);
2269
+ if (!e || e === "hold" || e.type !== "cubic-bezier") return void 0;
2270
+ return [e.x1, e.y1, e.x2, e.y2];
2271
+ };
2272
+ const animate = (segments, value, exact = true) => {
2273
+ const rest = value(0).map(round2);
2274
+ if (!segments.length) return { a: 0, k: rest.length === 1 ? rest[0] : rest };
2275
+ const keys = [];
2276
+ const key = (t, v, ease) => {
2277
+ const k = { t: frame(t), s: v.map(round2) };
2278
+ if (ease === "hold") k.h = 1;
2279
+ else {
2280
+ const [x1, y1, x2, y2] = ease === "linear" || !ease ? [0, 0, 1, 1] : ease;
2281
+ k.o = { x: [round2(x1)], y: [round2(y1)] };
2282
+ k.i = { x: [round2(x2)], y: [round2(y2)] };
2283
+ }
2284
+ if (keys.length && keys[keys.length - 1].t === k.t) keys[keys.length - 1] = k;
2285
+ else keys.push(k);
2286
+ };
2287
+ const times = [.../* @__PURE__ */ new Set([0, ...segments.flatMap((s) => [s.start, s.end]), end])].filter((t) => t >= 0 && t <= end).sort((a, b) => a - b);
2288
+ for (let n = 0; n + 1 < times.length; n++) {
2289
+ const a = times[n];
2290
+ const b = times[n + 1];
2291
+ const active = segments.filter((s) => s.start < b && s.end > a);
2292
+ if (!active.length) {
2293
+ key(a, value(a), "hold");
2294
+ continue;
2295
+ }
2296
+ const first = active[0];
2297
+ const curve = exact && active.every((s) => s.start === a && s.end === b && JSON.stringify(curveOf(s)) === JSON.stringify(curveOf(first))) ? curveOf(first) : void 0;
2298
+ if (curve) {
2299
+ key(a, value(a), curve);
2300
+ continue;
2301
+ }
2302
+ const count = Math.max(1, Math.ceil((b - a) * fps / 1e3));
2303
+ for (let k = 0; k < count; k++) {
2304
+ const t = a + (b - a) * k / count;
2305
+ key(t, value(t), "linear");
2306
+ sampled++;
2307
+ }
2308
+ }
2309
+ for (const s of segments) if (s.end <= s.start) key(s.start, value(s.start), "hold");
2310
+ keys.sort((x, y) => x.t - y.t);
2311
+ key(end, value(end), "hold");
2312
+ const last = keys[keys.length - 1];
2313
+ delete last.o;
2314
+ delete last.i;
2315
+ delete last.h;
2316
+ return { a: 1, k: keys };
2317
+ };
2318
+ const poseAt = (entry, t) => {
2319
+ const o = motionNodeOffset(entry, timeline, t, nodes);
2320
+ if (!o) return { p: [entry.center.x, entry.center.y], s: [100, 100], r: 0 };
2321
+ const m = o.matrix;
2322
+ const sx = Math.hypot(m[0], m[1]);
2323
+ const sy = sx > 1e-12 ? (m[0] * m[3] - m[1] * m[2]) / sx : 0;
2324
+ const c = entry.center;
2325
+ return { p: [m[0] * c.x + m[2] * c.y + m[4], m[1] * c.x + m[3] * c.y + m[5]], s: [sx * 100, sy * 100], r: Math.atan2(m[1], m[0]) * 180 / Math.PI };
2326
+ };
2327
+ const poseTransform = (entry) => {
2328
+ const id = entry.node.id;
2329
+ const tr = STATIC_TR();
2330
+ const c = [entry.center.x, entry.center.y].map(round2);
2331
+ const own = (p) => track(id, p);
2332
+ const layered = ["x", "y", "rotation", "scaleX", "scaleY"].some((p) => layers(id, p).length);
2333
+ const moving = ["x", "y", "rotation", "scaleX", "scaleY", "follow"].some((p) => own(p).length) || layered;
2334
+ if (moving) {
2335
+ const mixed = own("follow").length > 0 || layered;
2336
+ tr.a = { a: 0, k: c };
2337
+ tr.p = animate(mixed ? [...own("x"), ...own("y"), ...own("follow"), ...layers(id, "x"), ...layers(id, "y")] : [...own("x"), ...own("y")], (t) => poseAt(entry, t).p, !mixed);
2338
+ tr.r = animate(mixed ? [...own("rotation"), ...own("follow"), ...layers(id, "rotation")] : own("rotation"), (t) => [poseAt(entry, t).r], !mixed);
2339
+ tr.s = animate(mixed ? [...own("scaleX"), ...own("scaleY"), ...layers(id, "scaleX"), ...layers(id, "scaleY")] : [...own("scaleX"), ...own("scaleY")], (t) => poseAt(entry, t).s, !mixed);
2340
+ if (tr.p.a === 0) tr.p = { a: 0, k: poseAt(entry, 0).p.map(round2) };
2341
+ }
2342
+ const opacity = own("opacity");
2343
+ const rest = _nullishCoalesce(entry.node.opacity, () => ( 1));
2344
+ tr.o = opacity.length ? animate(opacity, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(opacity, t), () => ( rest))))) * 100]) : { a: 0, k: round2(rest * 100) };
2345
+ return tr;
2346
+ };
2347
+ const owner = (path, p) => {
2348
+ const entry = nodes.get(path.id);
2349
+ if (!entry) return void 0;
2350
+ return [...entry.ancestors, path.id].find((id) => timeline.tracks.has(motionTrackKey(id, p)));
2351
+ };
2352
+ const paintOf = (paint, what, id) => {
2353
+ if (paint === void 0 || paint === null || paint === "none") return null;
2354
+ const c = rgba(paint);
2355
+ if (!c) notes.push(`"${id}": its ${what} is a gradient, which is not exported; it is left unpainted.`);
2356
+ return c;
2357
+ };
2358
+ const pathShapes = (path) => {
2359
+ const items = [];
2360
+ const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
2361
+ const geometryAt = (t) => {
2362
+ if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2363
+ const now = _optionalChain([indexMotionNodes, 'call', _63 => _63(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _64 => _64.get, 'call', _65 => _65(path.id), 'optionalAccess', _66 => _66.node]);
2364
+ return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2365
+ };
2366
+ const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
2367
+ rest.forEach((contour, n) => {
2368
+ let ks = { a: 0, k: contour };
2369
+ if (shapeSegments.length) {
2370
+ const keys = [];
2371
+ let fits = true;
2372
+ const count = Math.max(1, Math.ceil(end * fps / 1e3));
2373
+ for (let k = 0; k <= count && fits; k++) {
2374
+ const t = end * k / count;
2375
+ const c = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(t)), round2)[n];
2376
+ if (!c || c.v.length !== contour.v.length || c.c !== contour.c) fits = false;
2377
+ else keys.push({ t: frame(t), s: [c], ...k < count ? { o: { x: [0], y: [0] }, i: { x: [1], y: [1] } } : {} });
2378
+ }
2379
+ if (fits) {
2380
+ ks = { a: 1, k: keys };
2381
+ sampled += keys.length;
2382
+ } else notes.push(`"${path.id}": its morph changes how many points the outline has, which Lottie cannot tween; it shows its starting shape.`);
2383
+ }
2384
+ items.push({ ty: "sh", nm: `${path.id} ${n + 1}`, ks });
2385
+ });
2386
+ const fillFrom = owner(path, "fill");
2387
+ const strokeFrom = owner(path, "stroke");
2388
+ const widthFrom = owner(path, "strokeWidth");
2389
+ const fadeFrom = owner(path, "fillOpacity");
2390
+ const drawFrom = owner(path, "drawEnd");
2391
+ if (drawFrom) {
2392
+ const draw = track(drawFrom, "drawEnd");
2393
+ 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 });
2394
+ }
2395
+ if (owner(path, "drawStart")) notes.push(`"${path.id}": drawing from a start other than 0 is not exported.`);
2396
+ const stroke = paintOf(path.stroke, "stroke", path.id);
2397
+ if (stroke || strokeFrom) {
2398
+ const colour = strokeFrom ? track(strokeFrom, "stroke") : [];
2399
+ items.push({
2400
+ ty: "st",
2401
+ nm: "Stroke",
2402
+ c: colour.length ? animate(colour, (t) => _nullishCoalesce(_nullishCoalesce(rgba(trackValue(colour, t)), () => ( stroke)), () => ( [0, 0, 0, 1]))) : { a: 0, k: (_nullishCoalesce(stroke, () => ( [0, 0, 0, 1]))).map(round2) },
2403
+ o: { a: 0, k: 100 },
2404
+ w: widthFrom ? animate(track(widthFrom, "strokeWidth"), (t) => [Math.max(0, Number(_nullishCoalesce(_nullishCoalesce(trackValue(track(widthFrom, "strokeWidth"), t), () => ( path.strokeWidth)), () => ( 1))))]) : { a: 0, k: round2(_nullishCoalesce(path.strokeWidth, () => ( 1))) },
2405
+ lc: path.strokeLinecap === "round" ? 2 : path.strokeLinecap === "square" ? 3 : 1,
2406
+ lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
2407
+ ml: 4
2408
+ });
2409
+ if (_optionalChain([path, 'access', _67 => _67.strokeDasharray, 'optionalAccess', _68 => _68.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2410
+ if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
2411
+ }
2412
+ const fill = paintOf(path.fill, "fill", path.id);
2413
+ if (fill || fillFrom) {
2414
+ const colour = fillFrom ? track(fillFrom, "fill") : [];
2415
+ const fade = fadeFrom ? track(fadeFrom, "fillOpacity") : [];
2416
+ items.push({
2417
+ ty: "fl",
2418
+ nm: "Fill",
2419
+ c: colour.length ? animate(colour, (t) => _nullishCoalesce(_nullishCoalesce(rgba(trackValue(colour, t)), () => ( fill)), () => ( [0, 0, 0, 1]))) : { a: 0, k: (_nullishCoalesce(fill, () => ( [0, 0, 0, 1]))).map(round2) },
2420
+ o: fade.length ? animate(fade, (t) => [Math.min(1, Math.max(0, Number(_nullishCoalesce(trackValue(fade, t), () => ( 1))))) * 100]) : { a: 0, k: 100 },
2421
+ r: path.fillRule === "evenodd" ? 2 : 1
2422
+ });
2423
+ }
2424
+ return items;
2425
+ };
2426
+ const nodeGroup = (node) => {
2427
+ const entry = nodes.get(node.id);
2428
+ if (timeline.tracks.has(motionTrackKey(node.id, "clipRect")) || timeline.tracks.has(motionTrackKey(node.id, "clipCircle")))
2429
+ notes.push(`"${node.id}": its wipe, iris or split reveal is not exported (Lottie masks belong to layers); it fades with its opacity only.`);
2430
+ let inner;
2431
+ const innerTr = STATIC_TR();
2432
+ if (node.type === "path") inner = [...pathShapes(node)];
2433
+ else {
2434
+ inner = [...node.children].reverse().map(nodeGroup);
2435
+ const m = _chunkYJLCBA7Vcjs.nodeMatrix.call(void 0, node);
2436
+ if (m !== _chunkYJLCBA7Vcjs.IDENTITY_MATRIX) {
2437
+ const parts = staticParts(m);
2438
+ if (!parts) notes.push(`"${node.id}": its transform skews, which is not exported; it is drawn without the skew.`);
2439
+ else {
2440
+ innerTr.p = { a: 0, k: parts.p.map(round2) };
2441
+ innerTr.s = { a: 0, k: parts.s.map(round2) };
2442
+ innerTr.r = { a: 0, k: round2(parts.r) };
2443
+ }
2444
+ }
2445
+ }
2446
+ const body = { ty: "gr", nm: `${node.id} (own)`, it: [...inner, innerTr] };
2447
+ return { ty: "gr", nm: _nullishCoalesce(node.name, () => ( node.id)), ...node.visible === false ? { hd: true } : {}, it: [body, poseTransform(entry)] };
2448
+ };
2449
+ const artwork = { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) };
2450
+ const content = [...artwork.nodes].reverse().map(nodeGroup);
2451
+ if (artwork.background && rgba(artwork.background))
2452
+ content.push({ ty: "gr", nm: "Background", it: [{ ty: "rc", nm: "Background", p: { a: 0, k: [artwork.width / 2, artwork.height / 2] }, s: { a: 0, k: [artwork.width, artwork.height] }, r: { a: 0, k: 0 } }, { ty: "fl", c: { a: 0, k: rgba(artwork.background).map(round2) }, o: { a: 0, k: 100 }, r: 1 }, STATIC_TR()] });
2453
+ const camera = track("", "camera");
2454
+ const root = STATIC_TR();
2455
+ if (camera.length) {
2456
+ const box = (t) => _nullishCoalesce(trackValue(camera, t), () => ( [0, 0, artwork.width, artwork.height]));
2457
+ root.a = animate(camera, (t) => box(t).slice(0, 2), false);
2458
+ root.s = animate(camera, (t) => [artwork.width / box(t)[2] * 100, artwork.height / box(t)[3] * 100], false);
2459
+ }
2460
+ const op = Math.max(1, Math.ceil(end * fps / 1e3));
2461
+ const lottie = {
2462
+ v: "5.7.4",
2463
+ fr: fps,
2464
+ ip: 0,
2465
+ op,
2466
+ w: artwork.width,
2467
+ h: artwork.height,
2468
+ nm: scene.name,
2469
+ ddd: 0,
2470
+ assets: [],
2471
+ layers: [
2472
+ {
2473
+ ddd: 0,
2474
+ ind: 1,
2475
+ ty: 4,
2476
+ nm: scene.name,
2477
+ sr: 1,
2478
+ ks: { o: { a: 0, k: 100 }, r: { a: 0, k: 0 }, p: { a: 0, k: [0, 0, 0] }, a: { a: 0, k: [0, 0, 0] }, s: { a: 0, k: [100, 100, 100] } },
2479
+ ao: 0,
2480
+ shapes: [{ ty: "gr", nm: "Camera", it: [...content, root] }],
2481
+ ip: 0,
2482
+ op,
2483
+ st: 0,
2484
+ bm: 0
2485
+ }
2486
+ ],
2487
+ markers: timeline.shots.map((s) => ({ tm: frame(s.start), cm: _nullishCoalesce(s.label, () => ( s.id)), dr: frame(s.end - s.start) }))
2488
+ };
2489
+ return { lottie, sampled, notes: [...new Set(notes)] };
2490
+ }
2491
+
2124
2492
  // src/animation-editor/controller.ts
2125
2493
  var BLANK = { width: 800, height: 450, background: "#ffffff", nodes: [] };
2126
2494
  var AnimationEditorController = (_class = class {
@@ -2188,7 +2556,7 @@ var AnimationEditorController = (_class = class {
2188
2556
  time: p.time,
2189
2557
  playing: p.playing,
2190
2558
  loop: p.loop,
2191
- currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _56 => _56(timeline, p.time), 'optionalAccess', _57 => _57.id]), () => ( null)),
2559
+ currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _69 => _69(timeline, p.time), 'optionalAccess', _70 => _70.id]), () => ( null)),
2192
2560
  selection: this.selection,
2193
2561
  readOnly: this.readOnly,
2194
2562
  error: this.error,
@@ -2227,7 +2595,7 @@ var AnimationEditorController = (_class = class {
2227
2595
  this.applying = false;
2228
2596
  }
2229
2597
  this.notify();
2230
- _optionalChain([this, 'access', _58 => _58.onChange, 'optionalCall', _59 => _59(scene)]);
2598
+ _optionalChain([this, 'access', _71 => _71.onChange, 'optionalCall', _72 => _72(scene)]);
2231
2599
  }
2232
2600
  commit(label, next) {
2233
2601
  const before = this.scene;
@@ -2495,7 +2863,12 @@ var SHOT_FIELDS = {
2495
2863
  camera: CAMERA,
2496
2864
  run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
2497
2865
  hold: MS("Hold after the commands (default 600)"),
2498
- speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." }
2866
+ speed: { type: "number", minimum: 0.05, maximum: 20, description: "Play the whole shot this many times as fast (default 1; 0.5 is half speed). fit_animation sets it." },
2867
+ at: object(
2868
+ { beat: { type: "number", minimum: 0, description: `Beat number on the scene's beat grid, from 0 (2.5 is the "and" of beat 3).` }, word: { type: "integer", minimum: 0, description: "Index of a narration word, from 0." }, ms: { type: "number", minimum: 0, description: "A time in ms." } },
2869
+ [],
2870
+ "Start this shot on a cue (give one of beat, word or ms); the shot before holds until then. get_timing_grid lists beats and words."
2871
+ )
2499
2872
  };
2500
2873
  var SHOT = object({ id: { type: "string", pattern: "^[A-Za-z0-9_][\\w.-]*$", description: 'Unique shot id, e.g. "intro".' }, ...SHOT_FIELDS }, ["id", "run"], "A shot.");
2501
2874
  var CONTROL = object(
@@ -2515,6 +2888,20 @@ var SETTINGS = {
2515
2888
  loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
2516
2889
  fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
2517
2890
  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).' },
2891
+ beat: {
2892
+ anyOf: [
2893
+ object({ bpm: { type: "number", minimum: 1, maximum: 1e3, description: "Beats a minute." }, offset: { type: "number", description: "When beat 0 falls, ms (default 0)." }, meter: { type: "integer", minimum: 1, description: "Beats a bar (default 4)." } }, ["bpm"]),
2894
+ { type: "null" }
2895
+ ],
2896
+ description: `A beat grid (music or a voiceover's rhythm) that shots can be cued to with "at": { "beat": n }; null clears.`
2897
+ },
2898
+ narration: {
2899
+ anyOf: [
2900
+ object({ words: { type: "array", items: object({ text: { type: "string", description: "The word." }, start: { type: "number", minimum: 0, description: "When it is said, ms." }, end: { type: "number", minimum: 0, description: "When it ends, ms." } }, ["text", "start"]), description: "Words in time order, from a TTS or speech-to-text word timing." } }, ["words"]),
2901
+ { type: "null" }
2902
+ ],
2903
+ description: `A voiceover's word timings that shots can be cued to with "at": { "word": index }; null clears.`
2904
+ },
2518
2905
  target: { anyOf: [{ type: "array", items: { type: "number", minimum: 0 }, minItems: 2, maxItems: 2 }, { type: "null" }], description: "Wanted length [min, max] in seconds; null clears." }
2519
2906
  };
2520
2907
  var EDIT_OP = {
@@ -2522,7 +2909,7 @@ var EDIT_OP = {
2522
2909
  oneOf: [
2523
2910
  object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
2524
2911
  object({ op: { const: "insert_shots", description: "Like insert_shots." }, shots: { type: "array", items: SHOT, minItems: 1, description: "Shots to add." }, at: { type: "integer", minimum: 0, description: "Position (default: the end)." } }, ["op", "shots"]),
2525
- object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." } }, ["op", "shot"]),
2912
+ object({ op: { const: "update_shot", description: "Like update_shot." }, shot: SHOT_ID, set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."), remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." } }, ["op", "shot"]),
2526
2913
  object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
2527
2914
  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"]),
2528
2915
  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"]),
@@ -2567,6 +2954,8 @@ function describeMotionShots(scene) {
2567
2954
  end: t.end,
2568
2955
  ...shot.camera ? { camera: shot.camera } : {},
2569
2956
  ...shot.hold !== void 0 ? { hold: shot.hold } : {},
2957
+ ...t.cue !== void 0 ? { cue: Math.round(t.cue) } : {},
2958
+ ...t.missedBy !== void 0 ? { missedBy: Math.round(t.missedBy) } : {},
2570
2959
  commands: shot.run.map((c, index) => {
2571
2960
  const ct = timeline.commands.find((x) => x.shotIndex === i && x.command === index);
2572
2961
  const primitive = motionFlairPrimitive(c);
@@ -2695,6 +3084,42 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2695
3084
  const CURVES_DESCRIPTION = "Write cubic-bezier easings (ease, ease-in, ease-out, ease-in-out, back eases, custom curves) as one change with ease: [x1, y1, x2, y2] (default true). Needs Flair 0.36 or later; false samples them into linear steps for older Flair.";
2696
3085
  const FLAIR_DESCRIPTION = "The Flair version to export for (default 0.39): springs and stepped time need 0.39, behaviours 0.38, cubic-bezier curves 0.36; older versions get those sampled into linear steps.";
2697
3086
  const BEHAVIORS_DESCRIPTION = "Write pulses, shakes, teeters, waves, wiggles and colour pulses as one change with a Flair behavior, so they play exactly (default true; needs Flair 0.38 or later). False samples them into linear steps for older Flair.";
3087
+ const getTimingGrid = {
3088
+ name: "get_timing_grid",
3089
+ title: "Read the beat grid and narration",
3090
+ description: 'Returns the beats (number, time in ms, bar and beat in the bar) and narration words (index, text, start, end) between two times, to cue shots to with "at". Also says which shot plays at each. Without a beat grid or narration, that part is empty and says how to add one.',
3091
+ inputSchema: object({
3092
+ from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
3093
+ to: { type: "number", minimum: 0, description: "Up to this time, ms (default: the end of the animation, or of the narration if later)." },
3094
+ limit: { type: "integer", minimum: 1, maximum: 1e3, description: "At most this many beats and words each (default 200)." }
3095
+ }),
3096
+ annotations: { title: "Read the beat grid and narration", ...READ },
3097
+ handler: ({ from = 0, to, limit = 200 }) => {
3098
+ const s = scene();
3099
+ const timeline = compileMotionScene(s);
3100
+ const words = _nullishCoalesce(_optionalChain([s, 'access', _73 => _73.narration, 'optionalAccess', _74 => _74.words]), () => ( []));
3101
+ const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
3102
+ const shotAt = (ms) => _optionalChain([timeline, 'access', _75 => _75.shots, 'access', _76 => _76.find, 'call', _77 => _77((x) => ms >= x.start && ms < x.end), 'optionalAccess', _78 => _78.id]);
3103
+ const beats = [];
3104
+ if (s.beat && s.beat.bpm > 0) {
3105
+ const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
3106
+ for (let b = first; beats.length < limit; b++) {
3107
+ const ms = motionBeatTime(s.beat, b);
3108
+ if (ms > end) break;
3109
+ const at = motionBeatAt(s.beat, ms);
3110
+ const shot = shotAt(ms);
3111
+ beats.push({ beat: b, ms: Math.round(ms), bar: at.bar, beatInBar: at.beatInBar, ...shot ? { shot } : {} });
3112
+ }
3113
+ }
3114
+ const listed = words.map((w, index) => ({ index, text: w.text, start: w.start, ...w.end !== void 0 ? { end: w.end } : {}, ...shotAt(w.start) ? { shot: shotAt(w.start) } : {} })).filter((w) => w.start >= from && w.start <= end).slice(0, limit);
3115
+ return {
3116
+ from,
3117
+ to: Math.round(end),
3118
+ ...s.beat ? { grid: s.beat, beats } : { beats: [], hint: 'No beat grid: set_animation_settings with { "beat": { "bpm": 120 } }.' },
3119
+ ...words.length ? { words: listed } : { words: [], narrationHint: 'No narration: set_animation_settings with { "narration": { "words": [{ "text", "start" }, \u2026] } }.' }
3120
+ };
3121
+ }
3122
+ };
2698
3123
  const exportFlairMotion = {
2699
3124
  name: "export_flair_motion",
2700
3125
  title: "Export as Flair vector motion",
@@ -2708,6 +3133,14 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2708
3133
  annotations: { title: "Export as Flair vector motion", ...READ },
2709
3134
  handler: ({ fps, curves, behaviors, flair }) => controller.toFlairMotion({ ...fps ? { fps } : {}, ...curves === void 0 ? {} : { curves }, ...behaviors === void 0 ? {} : { behaviors }, ...flair ? { flair } : {} })
2710
3135
  };
3136
+ const exportLottie = {
3137
+ name: "export_lottie",
3138
+ title: "Export as Lottie",
3139
+ description: "Returns the animation as Lottie (Bodymovin JSON, v5) for lottie-web and the mobile Lottie players: one shape layer whose groups mirror the artwork, a marker per shot, eased changes as keyframes with bezier easing and the rest sampled. Also returns how many keyframes were sampled and notes on anything not carried over (gradients, dashes, reveal clips, morphs that change point counts).",
3140
+ inputSchema: object({ fps: { type: "number", minimum: 1, maximum: 120, description: "Frame rate (default: the scene's fps, else 30)." } }),
3141
+ annotations: { title: "Export as Lottie", ...READ },
3142
+ handler: ({ fps }) => toLottie(scene(), fps ? { fps } : {})
3143
+ };
2711
3144
  const exportFlairEffect = {
2712
3145
  name: "export_flair_effect",
2713
3146
  title: "Export as a Flair effect",
@@ -2806,7 +3239,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2806
3239
  {
2807
3240
  shot: SHOT_ID,
2808
3241
  set: object({ id: SHOT.properties.id, ...SHOT_FIELDS }, [], "Fields to replace."),
2809
- remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed"] }, description: "Fields to clear." }
3242
+ remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." }
2810
3243
  },
2811
3244
  ["shot"]
2812
3245
  ),
@@ -2836,7 +3269,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2836
3269
  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"]),
2837
3270
  annotations: { title: "Add commands to a shot", ...WRITE },
2838
3271
  handler: ({ shot, commands, at }) => {
2839
- const before = _nullishCoalesce(_optionalChain([scene, 'call', _60 => _60(), 'access', _61 => _61.shots, 'access', _62 => _62.find, 'call', _63 => _63((s) => s.id === shot), 'optionalAccess', _64 => _64.run, 'access', _65 => _65.length]), () => ( 0));
3272
+ const before = _nullishCoalesce(_optionalChain([scene, 'call', _79 => _79(), 'access', _80 => _80.shots, 'access', _81 => _81.find, 'call', _82 => _82((s) => s.id === shot), 'optionalAccess', _83 => _83.run, 'access', _84 => _84.length]), () => ( 0));
2840
3273
  const first = _nullishCoalesce(at, () => ( before));
2841
3274
  return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
2842
3275
  const shots = describeMotionShots(s);
@@ -2872,7 +3305,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2872
3305
  const setSettings = {
2873
3306
  name: "set_animation_settings",
2874
3307
  title: "Change animation settings",
2875
- description: "Sets the name, description, loop mode, export frame rate, stepped time or target length. Returns the timing.",
3308
+ description: "Sets the name, description, loop mode, export frame rate, stepped time, beat grid, narration word timings or target length. Returns the timing.",
2876
3309
  inputSchema: object(SETTINGS),
2877
3310
  annotations: { title: "Change animation settings", ...WRITE_IDEMPOTENT },
2878
3311
  handler: (args) => apply({ op: "set_settings", ...args }, "Change settings")
@@ -2950,7 +3383,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2950
3383
  return { redone, ...redone ? { label } : {} };
2951
3384
  }
2952
3385
  };
2953
- const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
3386
+ const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
2954
3387
  const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
2955
3388
  return {
2956
3389
  id: _nullishCoalesce(options.id, () => ( "animation")),
@@ -2989,6 +3422,7 @@ Commands
2989
3422
  - Emphasis stacks on other motion: a shake during a slide shakes as it slides. Give move, rotate or scale "blend": "add" to stack them too (what they add stays); "blend": "replace" makes a change take over instead.
2990
3423
  - Any command but enter and exit can "repeat" (loop, mirror back and forth, or offset to carry on: rotate by 90, repeat 4 is a full turn); "duration" is one cycle.
2991
3424
  - Springs ("spring-gentle", "spring-quick", "spring-bouncy", "spring-slow" or { "type": "spring", \u2026 }) feel physical; leave "duration" out and the spring sets it.
3425
+ - To time shots to music or a voiceover: set a "beat" grid or "narration" word timings with set_animation_settings, read them with get_timing_grid, and give a shot "at": { "beat": n } or { "word": i }. The shot before holds until the cue; check_animation warns when a cue is missed.
2992
3426
  - "steps": 12 (on a command, or for the whole scene with set_animation_settings) holds poses "on twos" for a hand-drawn look. "geometric": true on scale or a camera zooms at an even rate.
2993
3427
  - Commands share their names and defaults with Flair's motion primitives (get_animation shows each one's "flairPrimitive"), and emphasis, reveals and follows export to Flair exactly.
2994
3428
  - effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).
@@ -3087,5 +3521,9 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
3087
3521
 
3088
3522
 
3089
3523
 
3090
- 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_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.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.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionClipAttrs = motionClipAttrs; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCycleDuration = motionCycleDuration; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPathLength = motionPathLength; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotTimeScale = motionShotTimeScale; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; 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.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
3524
+
3525
+
3526
+
3527
+
3528
+ 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_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.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.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; 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.motionCommandRepeat = motionCommandRepeat; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPathLength = motionPathLength; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotTimeScale = motionShotTimeScale; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; 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;
3091
3529
  //# sourceMappingURL=index.cjs.map