@pixerate/editor 0.62.0 → 0.63.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.
@@ -185,6 +185,7 @@ GridCraft. Apache POI is © The Apache Software Foundation, licensed under the A
185
185
 
186
186
  - `crates/engine/src/lib.rs` (`Engine::execute_merged`, `hold_merge`, `end_merge`) → `packages/core/src/history/HistoryManager.ts`
187
187
  - `crates/engine/src/lib.rs` (`Engine::is_dirty`, `same_document`, `mark_clean`) → `packages/core/src/dirty.ts`
188
+ - `crates/time/src/tempo.rs` and `grid.rs` (beats from a tempo and an offset, counted into bars by the meter) → `motionBeatTime` and `motionBeatAt` in `packages/core/src/animation-editor/compile.ts`; one steady tempo instead of a tempo map, with shots cued to beats instead of positions snapped to a grid
188
189
 
189
190
  ## vectorcraft
190
191
 
@@ -149,6 +149,8 @@ var MOTION_ISSUE_CODES = {
149
149
  "out-of-frame": { severity: "warning", wording: "A node a command animates ends up outside the camera." },
150
150
  "too-fast": { severity: "warning", wording: "A change is too quick to follow." },
151
151
  "spring-cut-short": { severity: "warning", wording: "A spring is given a duration shorter than it takes to settle, so it jumps to its end." },
152
+ "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." },
153
+ "cue-missed": { severity: "warning", wording: "A shot starts after its cue, because the shot before runs past it." },
152
154
  "scene-too-long": { severity: "warning", wording: "The scene runs past its target length." },
153
155
  "scene-too-short": { severity: "warning", wording: "The scene ends before its target length." },
154
156
  "unknown-control-color": { severity: "warning", wording: "A control stands for a colour the scene never uses." },
@@ -206,8 +208,8 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
206
208
  this.problems = problems;
207
209
  }
208
210
  };
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"];
211
+ var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "loop", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
212
+ var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
211
213
  var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
212
214
  var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
213
215
  var COMMAND_KEYS = {
@@ -361,6 +363,16 @@ function shotShapeProblems(shot, where) {
361
363
  if (shot.label !== void 0 && typeof shot.label !== "string") problems.push(`${name}: "label" must be a string.`);
362
364
  if (shot.hold !== void 0 && !(isNum(shot.hold) && shot.hold >= 0)) problems.push(`${name}: "hold" must be a number of milliseconds \u2265 0.`);
363
365
  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).`);
366
+ if (shot.at !== void 0) {
367
+ const at = shot.at;
368
+ const given = isObject(at) ? ["beat", "word", "ms"].filter((k) => at[k] !== void 0) : [];
369
+ if (!isObject(at) || given.length !== 1) problems.push(`${name}: "at" must give one of { "beat": number }, { "word": index } or { "ms": number }.`);
370
+ else {
371
+ unknownKeys(at, ["beat", "word", "ms"], `${name} at`, problems);
372
+ const k = given[0];
373
+ 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"}.`);
374
+ }
375
+ }
364
376
  if (shot.camera !== void 0) {
365
377
  const c = shot.camera;
366
378
  if (!isObject(c)) problems.push(`${name}: "camera" must be an object.`);
@@ -391,6 +403,16 @@ function motionSceneShapeProblems(input) {
391
403
  problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
392
404
  if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
393
405
  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.');
406
+ if (input.beat !== void 0) {
407
+ const b = input.beat;
408
+ 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))
409
+ problems.push('Scene: "beat" must be { "bpm": 1\u20131000, "offset"?: ms, "meter"?: beats a bar }.');
410
+ }
411
+ if (input.narration !== void 0) {
412
+ const n = input.narration;
413
+ 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))))))
414
+ problems.push('Scene: "narration" must be { "words": [{ "text", "start": ms, "end"?: ms }, \u2026] } in time order.');
415
+ }
394
416
  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
417
  if (input.target !== void 0 && !(Array.isArray(input.target) && input.target.length === 2 && input.target.every(isNum) && input.target[0] <= input.target[1]))
396
418
  problems.push('Scene: "target" must be [min, max] seconds.');
@@ -699,6 +721,21 @@ function motionPathLength(node, parentWorld = _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)
699
721
  function motionShotTimeScale(shot) {
700
722
  return typeof shot.speed === "number" && shot.speed > 0 && Number.isFinite(shot.speed) ? 1 / shot.speed : 1;
701
723
  }
724
+ function motionBeatTime(grid, beat) {
725
+ return (_nullishCoalesce(grid.offset, () => ( 0))) + beat * 6e4 / grid.bpm;
726
+ }
727
+ function motionBeatAt(grid, ms) {
728
+ const beat = (ms - (_nullishCoalesce(grid.offset, () => ( 0)))) * grid.bpm / 6e4;
729
+ const meter = Math.max(1, Math.round(_nullishCoalesce(grid.meter, () => ( 4))));
730
+ const whole = Math.floor(beat + 1e-9);
731
+ return { beat, bar: Math.floor(whole / meter) + 1, beatInBar: (whole % meter + meter) % meter + 1 };
732
+ }
733
+ function motionCueTime(scene, cue) {
734
+ if (typeof cue.ms === "number") return Math.max(0, cue.ms);
735
+ if (typeof cue.beat === "number") return scene.beat && scene.beat.bpm > 0 ? Math.max(0, motionBeatTime(scene.beat, cue.beat)) : void 0;
736
+ 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]);
737
+ return void 0;
738
+ }
702
739
  function motionCommandRepeat(command2) {
703
740
  if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
704
741
  const n = _nullishCoalesce(command2.repeat, () => ( 1));
@@ -736,6 +773,12 @@ function compileMotionScene(scene) {
736
773
  const cameraMoves = [];
737
774
  let t = 0;
738
775
  scene.shots.forEach((shot, index) => {
776
+ const cue = shot.at ? motionCueTime(scene, shot.at) : void 0;
777
+ if (cue !== void 0 && cue > t) {
778
+ if (shots.length) shots[shots.length - 1].end = cue;
779
+ t = cue;
780
+ }
781
+ const missedBy = cue !== void 0 && t - cue > 0.5 ? t - cue : void 0;
739
782
  const start = t;
740
783
  let cursor = start;
741
784
  const f = motionShotTimeScale(shot);
@@ -762,7 +805,7 @@ function compileMotionScene(scene) {
762
805
  runEnd = Math.max(runEnd, end2);
763
806
  });
764
807
  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 });
808
+ 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
809
  t = end;
767
810
  });
768
811
  const tracks = /* @__PURE__ */ new Map();
@@ -842,7 +885,7 @@ function compileMotionScene(scene) {
842
885
  const style = _nullishCoalesce(command2.style, () => ( "fade"));
843
886
  const opacity = rest("opacity");
844
887
  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);
888
+ 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
889
  if (reveal) {
847
890
  seg("opacity", opacity, "linear", start, start);
848
891
  seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
@@ -904,8 +947,8 @@ function compileMotionScene(scene) {
904
947
  }
905
948
  case "move": {
906
949
  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));
950
+ let dx = _nullishCoalesce(_optionalChain([command2, 'access', _23 => _23.by, 'optionalAccess', _24 => _24.x]), () => ( 0));
951
+ let dy = _nullishCoalesce(_optionalChain([command2, 'access', _25 => _25.by, 'optionalAccess', _26 => _26.y]), () => ( 0));
909
952
  if (command2.to) {
910
953
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
911
954
  const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
@@ -1009,7 +1052,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
1009
1052
  var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
1010
1053
  var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
1011
1054
  function applyMotionControlValues(scene, values) {
1012
- if (!values || !_optionalChain([scene, 'access', _23 => _23.controls, 'optionalAccess', _24 => _24.length])) return scene;
1055
+ if (!values || !_optionalChain([scene, 'access', _27 => _27.controls, 'optionalAccess', _28 => _28.length])) return scene;
1013
1056
  const map = /* @__PURE__ */ new Map();
1014
1057
  for (const control of scene.controls) {
1015
1058
  const value = values[control.name];
@@ -1055,7 +1098,7 @@ function shapeIn(entry, shape) {
1055
1098
  }
1056
1099
  function shapeAt(entry, timeline, nodes, time) {
1057
1100
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
1058
- if (!_optionalChain([track, 'optionalAccess', _25 => _25.length])) return null;
1101
+ if (!_optionalChain([track, 'optionalAccess', _29 => _29.length])) return null;
1059
1102
  const s = activeSegment(track, time);
1060
1103
  const fromId = String(s ? s.from : track[0].from);
1061
1104
  if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
@@ -1078,8 +1121,8 @@ function motionFollowRoute(entry, path) {
1078
1121
  }
1079
1122
  function followAt(entry, timeline, nodes, time) {
1080
1123
  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;
1124
+ const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _30 => _30[0]])));
1125
+ if (!_optionalChain([s, 'optionalAccess', _31 => _31.path])) return null;
1083
1126
  const route = motionFollowRoute(entry, nodes.get(s.path));
1084
1127
  const progress = Number(trackValue(track, time));
1085
1128
  if (!route || !(progress >= 0)) return null;
@@ -1139,7 +1182,7 @@ function motionFrameAt(scene, time, options = {}) {
1139
1182
  if (out.visible === void 0) delete out.visible;
1140
1183
  }
1141
1184
  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])) {
1185
+ if (effectTracks.length && _optionalChain([out, 'access', _32 => _32.effects, 'optionalAccess', _33 => _33.length])) {
1143
1186
  out = {
1144
1187
  ...out,
1145
1188
  effects: out.effects.map((effect) => {
@@ -1217,7 +1260,7 @@ function withViewBox(svg, box) {
1217
1260
  // src/animation-editor/check.ts
1218
1261
  var MOTION_MIN_CHANGE_MS = 120;
1219
1262
  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)]);
1263
+ const effect = _optionalChain([node, 'access', _34 => _34.effects, 'optionalAccess', _35 => _35.find, 'call', _36 => _36((e) => e.id === effectId)]);
1221
1264
  if (!effect) return null;
1222
1265
  return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
1223
1266
  };
@@ -1366,6 +1409,17 @@ function checkMotionScene(scene) {
1366
1409
  }
1367
1410
  });
1368
1411
  });
1412
+ scene.shots.forEach((shot, i) => {
1413
+ if (!shot.at) return;
1414
+ const timing = timeline.shots[i];
1415
+ if (timing.cue === void 0) {
1416
+ 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)`;
1417
+ issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
1418
+ } else if (timing.missedBy !== void 0) {
1419
+ const late = Math.round(timing.missedBy);
1420
+ 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" }));
1421
+ }
1422
+ });
1369
1423
  for (const [key, track] of timeline.tracks) {
1370
1424
  if (key.endsWith("\0add")) continue;
1371
1425
  for (let i = 1; i < track.length; i++) {
@@ -1392,7 +1446,7 @@ function checkMotionScene(scene) {
1392
1446
  const cam = frame.camera;
1393
1447
  const docIndex = indexMotionNodes(frame.document.nodes);
1394
1448
  for (const id of command2.targets) {
1395
- const b = _optionalChain([docIndex, 'access', _33 => _33.get, 'call', _34 => _34(id), 'optionalAccess', _35 => _35.bounds]);
1449
+ const b = _optionalChain([docIndex, 'access', _40 => _40.get, 'call', _41 => _41(id), 'optionalAccess', _42 => _42.bounds]);
1396
1450
  if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
1397
1451
  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
1452
  if (outside) {
@@ -1467,6 +1521,14 @@ function applyOne(scene, edit) {
1467
1521
  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
1522
  next.steps = edit.steps;
1469
1523
  }
1524
+ for (const key of ["beat", "narration"]) {
1525
+ if (edit[key] === null) delete next[key];
1526
+ else if (edit[key] !== void 0) next[key] = edit[key];
1527
+ }
1528
+ {
1529
+ const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"/.test(p));
1530
+ if (problems.length) fail(problems.join(" "));
1531
+ }
1470
1532
  if (edit.target === null) delete next.target;
1471
1533
  else if (edit.target !== void 0) next.target = edit.target;
1472
1534
  return next;
@@ -1574,9 +1636,9 @@ function applyMotionEdits(scene, edits) {
1574
1636
  let next = scene;
1575
1637
  for (let i = 0; i < list.length; i++) {
1576
1638
  const edit = list[i];
1577
- const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _36 => _36.op])}): ` : "";
1639
+ const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _43 => _43.op])}): ` : "";
1578
1640
  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.` };
1641
+ return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _44 => _44.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
1580
1642
  try {
1581
1643
  next = applyOne(next, edit);
1582
1644
  } catch (error) {
@@ -1736,7 +1798,7 @@ function toFlairSvgMotion(scene, options = {}) {
1736
1798
  const wrapper = freshId(`${id}--follow`);
1737
1799
  const centre = freshId(`${id}--follow-centre`);
1738
1800
  const c = entry.center;
1739
- const at = _optionalChain([motionNodeOffset, 'call', _38 => _38(entry, timeline, 0, nodes), 'optionalAccess', _39 => _39.follow]);
1801
+ const at = _optionalChain([motionNodeOffset, 'call', _45 => _45(entry, timeline, 0, nodes), 'optionalAccess', _46 => _46.follow]);
1740
1802
  const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
1741
1803
  (wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
1742
1804
  follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
@@ -1796,7 +1858,7 @@ function toFlairSvgMotion(scene, options = {}) {
1796
1858
  exact: () => false,
1797
1859
  value: (t) => {
1798
1860
  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]));
1861
+ 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
1862
  if (!now || now.type !== "path") return "";
1801
1863
  const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
1802
1864
  return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
@@ -1817,8 +1879,8 @@ function toFlairSvgMotion(scene, options = {}) {
1817
1879
  ...delay > 0 ? { delay: round2(delay) } : {},
1818
1880
  ms: round2(ms),
1819
1881
  ease: written,
1820
- ..._optionalChain([segment, 'optionalAccess', _44 => _44.behavior]) ? { behavior: segment.behavior } : {},
1821
- ..._optionalChain([segment, 'optionalAccess', _45 => _45.steps]) ? { strobe: segment.steps } : {}
1882
+ ..._optionalChain([segment, 'optionalAccess', _51 => _51.behavior]) ? { behavior: segment.behavior } : {},
1883
+ ..._optionalChain([segment, 'optionalAccess', _52 => _52.steps]) ? { strobe: segment.steps } : {}
1822
1884
  };
1823
1885
  if (channel.kind === "view") animate.push({ ...base, view: to });
1824
1886
  else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
@@ -1891,7 +1953,7 @@ function toFlairSvgMotion(scene, options = {}) {
1891
1953
  if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
1892
1954
  const lastChange = animate.reduce((m, a) => Math.max(m, (_nullishCoalesce(a.delay, () => ( 0))) + (_nullishCoalesce(a.ms, () => ( 0)))), 0);
1893
1955
  let motion = { svg, animate };
1894
- if (_optionalChain([scene, 'access', _46 => _46.controls, 'optionalAccess', _47 => _47.length])) {
1956
+ if (_optionalChain([scene, 'access', _53 => _53.controls, 'optionalAccess', _54 => _54.length])) {
1895
1957
  const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
1896
1958
  const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
1897
1959
  svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
@@ -1913,7 +1975,7 @@ function toFlairVectorEffect(scene, options = {}) {
1913
1975
  tags: _nullishCoalesce(options.tags, () => ( [])),
1914
1976
  motion: out.motion,
1915
1977
  hold: out.hold,
1916
- ..._optionalChain([scene, 'access', _48 => _48.presets, 'optionalAccess', _49 => _49.length]) ? { presets: scene.presets } : {},
1978
+ ..._optionalChain([scene, 'access', _55 => _55.presets, 'optionalAccess', _56 => _56.length]) ? { presets: scene.presets } : {},
1917
1979
  ...options.trigger ? { trigger: options.trigger } : {}
1918
1980
  };
1919
1981
  }
@@ -1956,7 +2018,7 @@ function resolvePlaceholders(motion, notes) {
1956
2018
  const filled = text.replace(PLACEHOLDER, (_, name) => {
1957
2019
  const d = declared.get(name);
1958
2020
  if (!d) unknown.add(name);
1959
- return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _50 => _50.value]), () => ( ""))).replace(/"/g, '\\"');
2021
+ return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _57 => _57.value]), () => ( ""))).replace(/"/g, '\\"');
1960
2022
  });
1961
2023
  for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
1962
2024
  return { motion: JSON.parse(filled), controls };
@@ -2010,7 +2072,7 @@ function commandsFor(anim, nodes, entered, notes) {
2010
2072
  if (anim.along) {
2011
2073
  const path = nodes.get(anim.along);
2012
2074
  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) });
2075
+ 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
2076
  }
2015
2077
  for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
2016
2078
  if (attr === "opacity") {
@@ -2021,7 +2083,7 @@ function commandsFor(anim, nodes, entered, notes) {
2021
2083
  out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
2022
2084
  } else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
2023
2085
  } 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") });
2086
+ 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
2087
  else {
2026
2088
  if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
2027
2089
  out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
@@ -2188,7 +2250,7 @@ var AnimationEditorController = (_class = class {
2188
2250
  time: p.time,
2189
2251
  playing: p.playing,
2190
2252
  loop: p.loop,
2191
- currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _56 => _56(timeline, p.time), 'optionalAccess', _57 => _57.id]), () => ( null)),
2253
+ currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _63 => _63(timeline, p.time), 'optionalAccess', _64 => _64.id]), () => ( null)),
2192
2254
  selection: this.selection,
2193
2255
  readOnly: this.readOnly,
2194
2256
  error: this.error,
@@ -2227,7 +2289,7 @@ var AnimationEditorController = (_class = class {
2227
2289
  this.applying = false;
2228
2290
  }
2229
2291
  this.notify();
2230
- _optionalChain([this, 'access', _58 => _58.onChange, 'optionalCall', _59 => _59(scene)]);
2292
+ _optionalChain([this, 'access', _65 => _65.onChange, 'optionalCall', _66 => _66(scene)]);
2231
2293
  }
2232
2294
  commit(label, next) {
2233
2295
  const before = this.scene;
@@ -2495,7 +2557,12 @@ var SHOT_FIELDS = {
2495
2557
  camera: CAMERA,
2496
2558
  run: { type: "array", items: MOTION_COMMAND_SCHEMA, description: "Commands, in order." },
2497
2559
  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." }
2560
+ 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." },
2561
+ at: object(
2562
+ { 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." } },
2563
+ [],
2564
+ "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."
2565
+ )
2499
2566
  };
2500
2567
  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
2568
  var CONTROL = object(
@@ -2515,6 +2582,20 @@ var SETTINGS = {
2515
2582
  loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
2516
2583
  fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
2517
2584
  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).' },
2585
+ beat: {
2586
+ anyOf: [
2587
+ 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"]),
2588
+ { type: "null" }
2589
+ ],
2590
+ description: `A beat grid (music or a voiceover's rhythm) that shots can be cued to with "at": { "beat": n }; null clears.`
2591
+ },
2592
+ narration: {
2593
+ anyOf: [
2594
+ 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"]),
2595
+ { type: "null" }
2596
+ ],
2597
+ description: `A voiceover's word timings that shots can be cued to with "at": { "word": index }; null clears.`
2598
+ },
2518
2599
  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
2600
  };
2520
2601
  var EDIT_OP = {
@@ -2522,7 +2603,7 @@ var EDIT_OP = {
2522
2603
  oneOf: [
2523
2604
  object({ op: { const: "set_settings", description: "Like set_animation_settings." }, ...SETTINGS }, ["op"]),
2524
2605
  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"]),
2606
+ 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
2607
  object({ op: { const: "delete_shots", description: "Like delete_shots." }, shots: { type: "array", items: SHOT_ID, minItems: 1, description: "Shot ids." } }, ["op", "shots"]),
2527
2608
  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
2609
  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 +2648,8 @@ function describeMotionShots(scene) {
2567
2648
  end: t.end,
2568
2649
  ...shot.camera ? { camera: shot.camera } : {},
2569
2650
  ...shot.hold !== void 0 ? { hold: shot.hold } : {},
2651
+ ...t.cue !== void 0 ? { cue: Math.round(t.cue) } : {},
2652
+ ...t.missedBy !== void 0 ? { missedBy: Math.round(t.missedBy) } : {},
2570
2653
  commands: shot.run.map((c, index) => {
2571
2654
  const ct = timeline.commands.find((x) => x.shotIndex === i && x.command === index);
2572
2655
  const primitive = motionFlairPrimitive(c);
@@ -2695,6 +2778,42 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2695
2778
  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
2779
  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
2780
  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.";
2781
+ const getTimingGrid = {
2782
+ name: "get_timing_grid",
2783
+ title: "Read the beat grid and narration",
2784
+ 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.',
2785
+ inputSchema: object({
2786
+ from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
2787
+ to: { type: "number", minimum: 0, description: "Up to this time, ms (default: the end of the animation, or of the narration if later)." },
2788
+ limit: { type: "integer", minimum: 1, maximum: 1e3, description: "At most this many beats and words each (default 200)." }
2789
+ }),
2790
+ annotations: { title: "Read the beat grid and narration", ...READ },
2791
+ handler: ({ from = 0, to, limit = 200 }) => {
2792
+ const s = scene();
2793
+ const timeline = compileMotionScene(s);
2794
+ const words = _nullishCoalesce(_optionalChain([s, 'access', _67 => _67.narration, 'optionalAccess', _68 => _68.words]), () => ( []));
2795
+ const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
2796
+ const shotAt = (ms) => _optionalChain([timeline, 'access', _69 => _69.shots, 'access', _70 => _70.find, 'call', _71 => _71((x) => ms >= x.start && ms < x.end), 'optionalAccess', _72 => _72.id]);
2797
+ const beats = [];
2798
+ if (s.beat && s.beat.bpm > 0) {
2799
+ const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
2800
+ for (let b = first; beats.length < limit; b++) {
2801
+ const ms = motionBeatTime(s.beat, b);
2802
+ if (ms > end) break;
2803
+ const at = motionBeatAt(s.beat, ms);
2804
+ const shot = shotAt(ms);
2805
+ beats.push({ beat: b, ms: Math.round(ms), bar: at.bar, beatInBar: at.beatInBar, ...shot ? { shot } : {} });
2806
+ }
2807
+ }
2808
+ 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);
2809
+ return {
2810
+ from,
2811
+ to: Math.round(end),
2812
+ ...s.beat ? { grid: s.beat, beats } : { beats: [], hint: 'No beat grid: set_animation_settings with { "beat": { "bpm": 120 } }.' },
2813
+ ...words.length ? { words: listed } : { words: [], narrationHint: 'No narration: set_animation_settings with { "narration": { "words": [{ "text", "start" }, \u2026] } }.' }
2814
+ };
2815
+ }
2816
+ };
2698
2817
  const exportFlairMotion = {
2699
2818
  name: "export_flair_motion",
2700
2819
  title: "Export as Flair vector motion",
@@ -2806,7 +2925,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2806
2925
  {
2807
2926
  shot: SHOT_ID,
2808
2927
  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." }
2928
+ remove: { type: "array", items: { type: "string", enum: ["label", "camera", "hold", "speed", "at"] }, description: "Fields to clear." }
2810
2929
  },
2811
2930
  ["shot"]
2812
2931
  ),
@@ -2836,7 +2955,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2836
2955
  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
2956
  annotations: { title: "Add commands to a shot", ...WRITE },
2838
2957
  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));
2958
+ const before = _nullishCoalesce(_optionalChain([scene, 'call', _73 => _73(), 'access', _74 => _74.shots, 'access', _75 => _75.find, 'call', _76 => _76((s) => s.id === shot), 'optionalAccess', _77 => _77.run, 'access', _78 => _78.length]), () => ( 0));
2840
2959
  const first = _nullishCoalesce(at, () => ( before));
2841
2960
  return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
2842
2961
  const shots = describeMotionShots(s);
@@ -2872,7 +2991,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2872
2991
  const setSettings = {
2873
2992
  name: "set_animation_settings",
2874
2993
  title: "Change animation settings",
2875
- description: "Sets the name, description, loop mode, export frame rate, stepped time or target length. Returns the timing.",
2994
+ description: "Sets the name, description, loop mode, export frame rate, stepped time, beat grid, narration word timings or target length. Returns the timing.",
2876
2995
  inputSchema: object(SETTINGS),
2877
2996
  annotations: { title: "Change animation settings", ...WRITE_IDEMPOTENT },
2878
2997
  handler: (args) => apply({ op: "set_settings", ...args }, "Change settings")
@@ -2950,7 +3069,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
2950
3069
  return { redone, ...redone ? { label } : {} };
2951
3070
  }
2952
3071
  };
2953
- const reads = [getAnimation, getShot, findElements, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
3072
+ const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, seekAnimation];
2954
3073
  const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
2955
3074
  return {
2956
3075
  id: _nullishCoalesce(options.id, () => ( "animation")),
@@ -2989,6 +3108,7 @@ Commands
2989
3108
  - 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
3109
  - 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
3110
  - Springs ("spring-gentle", "spring-quick", "spring-bouncy", "spring-slow" or { "type": "spring", \u2026 }) feel physical; leave "duration" out and the spring sets it.
3111
+ - 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
3112
  - "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
3113
  - 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
3114
  - effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).
@@ -3087,5 +3207,8 @@ Keep motion purposeful: one thing moving at a time reads best, ease-out for arri
3087
3207
 
3088
3208
 
3089
3209
 
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;
3210
+
3211
+
3212
+
3213
+ 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.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
3091
3214
  //# sourceMappingURL=index.cjs.map