@pixerate/editor 0.71.0 → 0.73.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.
@@ -56,6 +56,7 @@ var _chunk2FHLHZALcjs = require('../chunk-2FHLHZAL.cjs');
56
56
 
57
57
  // src/animation-editor/types.ts
58
58
  var MOTION_TRIGGERS = ["click", "hover", "press", "scroll"];
59
+ var MOTION_DRIVEN_PROPERTIES = ["x", "y", "rotation", "scaleX", "scaleY", "opacity"];
59
60
  var MOTION_CLIP_TARGET = "$";
60
61
  var MOTION_COMMAND_TYPES = [
61
62
  "enter",
@@ -141,6 +142,7 @@ var MOTION_ISSUE_CODES = {
141
142
  "unknown-path": { severity: "error", wording: "A follow command names a path the artwork does not have." },
142
143
  "not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
143
144
  "reveal-no-box": { severity: "warning", wording: "A wipe, iris or split cannot clip a node with no width or height (a lone straight line), so it fades instead." },
145
+ "driver-cycle": { severity: "error", wording: "Drivers follow each other in a loop, so none of them has a value." },
144
146
  "unknown-clip": { severity: "error", wording: "A play command names a clip the scene does not have." },
145
147
  "unknown-shape": { severity: "error", wording: "A morph names a shape the artwork does not have." },
146
148
  "match-self": { severity: "error", wording: "A match turns a group into itself or into a group it holds (or that holds it)." },
@@ -219,7 +221,7 @@ ${problems.map((p) => `- ${p}`).join("\n")}`);
219
221
  this.problems = problems;
220
222
  }
221
223
  };
222
- var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "clips", "loop", "trigger", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
224
+ var SCENE_KEYS = ["version", "name", "description", "artwork", "shots", "clips", "drivers", "motionBlur", "loop", "trigger", "fps", "steps", "beat", "narration", "target", "controls", "presets"];
223
225
  var SHOT_KEYS = ["id", "label", "camera", "at", "run", "hold", "speed"];
224
226
  var CAMERA_KEYS = ["x", "y", "width", "height", "frame", "padding", "ease", "ms", "geometric"];
225
227
  var BASE_KEYS = ["type", "targets", "duration", "delay", "stagger", "with", "ease", "note", "repeat", "repeatMode", "steps"];
@@ -403,6 +405,23 @@ function clipsShapeProblems(clips) {
403
405
  }
404
406
  return problems;
405
407
  }
408
+ function driversShapeProblems(drivers) {
409
+ if (!Array.isArray(drivers)) return ['Scene: "drivers" must be a list of { target, property, source: { node, property }, factor?, offset?, clamp? }.'];
410
+ const problems = [];
411
+ const props = MOTION_DRIVEN_PROPERTIES;
412
+ drivers.forEach((d, i) => {
413
+ const where = `Driver ${i + 1}`;
414
+ if (!isObject(d)) return void problems.push(`${where} must be an object.`);
415
+ unknownKeys(d, ["target", "property", "source", "factor", "offset", "clamp"], where, problems);
416
+ if (typeof d.target !== "string" || !d.target) problems.push(`${where}: "target" must be the id of the node that follows.`);
417
+ if (!props.includes(d.property)) problems.push(`${where}: "property" must be one of ${MOTION_DRIVEN_PROPERTIES.join(", ")}.`);
418
+ if (!isObject(d.source) || typeof d.source.node !== "string" || !d.source.node || !props.includes(d.source.property)) problems.push(`${where}: "source" must be { "node": id, "property": one of ${MOTION_DRIVEN_PROPERTIES.join(", ")} }.`);
419
+ if (d.factor !== void 0 && !isNum(d.factor)) problems.push(`${where}: "factor" must be a number.`);
420
+ if (d.offset !== void 0 && !isNum(d.offset)) problems.push(`${where}: "offset" must be a number.`);
421
+ if (d.clamp !== void 0 && !(Array.isArray(d.clamp) && d.clamp.length === 2 && d.clamp.every(isNum) && d.clamp[0] <= d.clamp[1])) problems.push(`${where}: "clamp" must be [min, max].`);
422
+ });
423
+ return problems;
424
+ }
406
425
  function shotShapeProblems(shot, where) {
407
426
  if (!isObject(shot)) return [`${where} must be an object.`];
408
427
  const problems = [];
@@ -451,6 +470,12 @@ function motionSceneShapeProblems(input) {
451
470
  if (!isObject(art) || !isNum(art.width) || !isNum(art.height) || !(art.width > 0) || !(art.height > 0) || !Array.isArray(art.nodes))
452
471
  problems.push('Scene: "artwork" must be a vector document { width, height, nodes }.');
453
472
  if (input.clips !== void 0) problems.push(...clipsShapeProblems(input.clips));
473
+ if (input.drivers !== void 0) problems.push(...driversShapeProblems(input.drivers));
474
+ if (input.motionBlur !== void 0) {
475
+ const b = input.motionBlur;
476
+ if (!isObject(b) || Object.keys(b).some((k) => k !== "shutter" && k !== "samples") || b.shutter !== void 0 && !(isNum(b.shutter) && b.shutter > 0 && b.shutter <= 720) || b.samples !== void 0 && !(Number.isInteger(b.samples) && b.samples >= 2 && b.samples <= 32))
477
+ problems.push('Scene: "motionBlur" must be { "shutter"?: degrees above 0 up to 720 (default 180), "samples"?: 2 to 32 (default 8) }.');
478
+ }
454
479
  if (input.trigger !== void 0 && !MOTION_TRIGGERS.includes(input.trigger)) problems.push(`Scene: "trigger" must be one of ${MOTION_TRIGGERS.join(", ")}.`);
455
480
  if (input.loop !== void 0 && !["none", "loop", "ping-pong"].includes(input.loop)) problems.push('Scene: "loop" must be "none", "loop" or "ping-pong".');
456
481
  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.');
@@ -826,6 +851,41 @@ function motionCueTime(scene, cue) {
826
851
  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]);
827
852
  return void 0;
828
853
  }
854
+ var SCALE_LIKE = /* @__PURE__ */ new Set(["scaleX", "scaleY", "opacity"]);
855
+ function motionPropertyValue(timeline, node, property, time, raw = false, depth = 0) {
856
+ const rest = property === "opacity" ? _nullishCoalesce(_optionalChain([timeline, 'access', _22 => _22.restOpacity, 'optionalAccess', _23 => _23.get, 'call', _24 => _24(node)]), () => ( 1)) : SCALE_LIKE.has(property) ? 1 : 0;
857
+ const v = trackValue(timeline.tracks.get(motionTrackKey(node, property)), time, raw);
858
+ const base = property === "opacity" ? typeof v === "number" ? v : rest : withMotionLayers(timeline, node, property, typeof v === "number" ? v : rest, time, raw);
859
+ return applyMotionDrivers(timeline, node, property, base, time, raw, depth);
860
+ }
861
+ function applyMotionDrivers(timeline, node, property, value, time, raw = false, depth = 0) {
862
+ if (!timeline.drivers || depth > 8) return value;
863
+ let v = value;
864
+ for (const d of timeline.drivers) {
865
+ if (d.target !== node || d.property !== property) continue;
866
+ const src = motionPropertyValue(timeline, d.source.node, d.source.property, time, raw, depth + 1);
867
+ const srcRest = d.source.property === "opacity" ? _nullishCoalesce(_optionalChain([timeline, 'access', _25 => _25.restOpacity, 'optionalAccess', _26 => _26.get, 'call', _27 => _27(d.source.node)]), () => ( 1)) : SCALE_LIKE.has(d.source.property) ? 1 : 0;
868
+ let change = (src - srcRest) * (_nullishCoalesce(d.factor, () => ( 1))) + (_nullishCoalesce(d.offset, () => ( 0)));
869
+ if (d.clamp) change = Math.min(d.clamp[1], Math.max(d.clamp[0], change));
870
+ v = SCALE_LIKE.has(property) ? v * (1 + change) : v + change;
871
+ }
872
+ return property === "opacity" ? Math.min(1, Math.max(0, v)) : v;
873
+ }
874
+ function motionDrivenNodes(timeline) {
875
+ return new Set((_nullishCoalesce(timeline.drivers, () => ( []))).map((d) => d.target));
876
+ }
877
+ function motionDriverSources(timeline, node, properties, seen = /* @__PURE__ */ new Set()) {
878
+ const out = [];
879
+ for (const d of _nullishCoalesce(timeline.drivers, () => ( []))) {
880
+ if (d.target !== node || !properties.includes(d.property)) continue;
881
+ const key = `${d.source.node}\0${d.source.property}`;
882
+ if (seen.has(key)) continue;
883
+ seen.add(key);
884
+ out.push(..._nullishCoalesce(timeline.tracks.get(motionTrackKey(d.source.node, d.source.property)), () => ( [])), ..._nullishCoalesce(timeline.tracks.get(`${motionTrackKey(d.source.node, d.source.property)}\0add`), () => ( [])));
885
+ out.push(...motionDriverSources(timeline, d.source.node, [d.source.property], seen));
886
+ }
887
+ return out;
888
+ }
829
889
  function motionCommandRepeat(command2) {
830
890
  if (command2.type === "wait" || command2.type === "enter" || command2.type === "exit") return 1;
831
891
  const n = _nullishCoalesce(command2.repeat, () => ( 1));
@@ -835,7 +895,7 @@ function motionCommandDuration(command2, scene, target) {
835
895
  return motionCycleDuration(command2, scene, target) * motionCommandRepeat(command2);
836
896
  }
837
897
  function motionClipLayout(scene, clip, target, speed = 1) {
838
- const run = _optionalChain([scene, 'access', _22 => _22.clips, 'optionalAccess', _23 => _23[clip], 'optionalAccess', _24 => _24.run]);
898
+ const run = _optionalChain([scene, 'access', _28 => _28.clips, 'optionalAccess', _29 => _29[clip], 'optionalAccess', _30 => _30.run]);
839
899
  if (!run) return { items: [], length: 0 };
840
900
  const f = speed > 0 && Number.isFinite(speed) ? 1 / speed : 1;
841
901
  const items = [];
@@ -877,7 +937,7 @@ function motionCycleDuration(command2, scene, target) {
877
937
  const ms = length * MOTION_DRAW_MS_PER_UNIT;
878
938
  return Math.round(Math.min(MOTION_DRAW_RANGE[1], Math.max(MOTION_DRAW_RANGE[0], ms)));
879
939
  }
880
- if (command2.type === "move" && _optionalChain([command2, 'access', _25 => _25.via, 'optionalAccess', _26 => _26.length])) return MOTION_DEFAULT_DURATIONS.move + 400 * command2.via.length;
940
+ if (command2.type === "move" && _optionalChain([command2, 'access', _31 => _31.via, 'optionalAccess', _32 => _32.length])) return MOTION_DEFAULT_DURATIONS.move + 400 * command2.via.length;
881
941
  return MOTION_DEFAULT_DURATIONS[command2.type];
882
942
  }
883
943
  var MOTION_EASE_OUT = { type: "cubic-bezier", x1: 1 / 3, y1: 1, x2: 2 / 3, y2: 1 };
@@ -1023,11 +1083,11 @@ function compileMotionScene(scene) {
1023
1083
  const at = nodes.get(p.id);
1024
1084
  let first = { x: 0, y: 0 };
1025
1085
  try {
1026
- const m = _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, p, _nullishCoalesce(_optionalChain([at, 'optionalAccess', _27 => _27.parentWorld]), () => ( _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)), { effects: false })[0];
1086
+ const m = _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, p, _nullishCoalesce(_optionalChain([at, 'optionalAccess', _33 => _33.parentWorld]), () => ( _chunkYJLCBA7Vcjs.IDENTITY_MATRIX)), { effects: false })[0];
1027
1087
  if (m && m.type === "M") first = { x: m.x, y: m.y };
1028
1088
  } catch (e3) {
1029
1089
  }
1030
- return { id: p.id, len: Math.max(1e-6, motionPathLength(p, _optionalChain([at, 'optionalAccess', _28 => _28.parentWorld]))), first };
1090
+ return { id: p.id, len: Math.max(1e-6, motionPathLength(p, _optionalChain([at, 'optionalAccess', _34 => _34.parentWorld]))), first };
1031
1091
  });
1032
1092
  if (!list.length) return null;
1033
1093
  const span = Math.max(0, e - s);
@@ -1056,7 +1116,7 @@ function compileMotionScene(scene) {
1056
1116
  const style = _nullishCoalesce(command2.style, () => ( "fade"));
1057
1117
  const opacity = rest("opacity");
1058
1118
  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;
1059
- for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _29 => _29.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
1119
+ for (const p of ["clipRect", "clipCircle"]) if (p !== _optionalChain([reveal, 'optionalAccess', _35 => _35.property]) && tracks.has(motionTrackKey(node, p))) seg(p, rest(p), "linear", start, start);
1060
1120
  if (reveal) {
1061
1121
  seg("opacity", opacity, "linear", start, start);
1062
1122
  seg(reveal.property, reveal.to, _nullishCoalesce(ease, () => ( MOTION_EASE_OUT)), start, end, { from: reveal.from });
@@ -1132,15 +1192,15 @@ function compileMotionScene(scene) {
1132
1192
  }
1133
1193
  case "move": {
1134
1194
  const e = _nullishCoalesce(ease, () => ( MOTION_EASE_IN_OUT));
1135
- let dx = _nullishCoalesce(_optionalChain([command2, 'access', _30 => _30.by, 'optionalAccess', _31 => _31.x]), () => ( 0));
1136
- let dy = _nullishCoalesce(_optionalChain([command2, 'access', _32 => _32.by, 'optionalAccess', _33 => _33.y]), () => ( 0));
1195
+ let dx = _nullishCoalesce(_optionalChain([command2, 'access', _36 => _36.by, 'optionalAccess', _37 => _37.x]), () => ( 0));
1196
+ let dy = _nullishCoalesce(_optionalChain([command2, 'access', _38 => _38.by, 'optionalAccess', _39 => _39.y]), () => ( 0));
1137
1197
  if (command2.to) {
1138
1198
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
1139
1199
  const target = _chunkYJLCBA7Vcjs.applyMatrix.call(void 0, inv, command2.to);
1140
1200
  dx = target.x - (entry.center.x + num("x"));
1141
1201
  dy = target.y - (entry.center.y + num("y"));
1142
1202
  }
1143
- if (_optionalChain([command2, 'access', _34 => _34.via, 'optionalAccess', _35 => _35.length])) {
1203
+ if (_optionalChain([command2, 'access', _40 => _40.via, 'optionalAccess', _41 => _41.length])) {
1144
1204
  const inv = _chunkYJLCBA7Vcjs.invertMatrix.call(void 0, entry.parentWorld);
1145
1205
  const x0 = num("x");
1146
1206
  const y0 = num("y");
@@ -1238,7 +1298,7 @@ function compileMotionScene(scene) {
1238
1298
  for (const track of tracks.values()) track.sort((a, b) => a.start - b.start);
1239
1299
  segments.sort((a, b) => a.start - b.start);
1240
1300
  const duration = shots.length ? shots[shots.length - 1].end : 0;
1241
- const timeline = { duration, shots, commands, segments, tracks };
1301
+ const timeline = { duration, shots, commands, segments, tracks, ..._optionalChain([scene, 'access', _42 => _42.drivers, 'optionalAccess', _43 => _43.length]) ? { drivers: scene.drivers, restOpacity: new Map([...nodes].map(([id, e]) => [id, _nullishCoalesce(e.node.opacity, () => ( 1))])) } : {} };
1242
1302
  timelineCache.set(scene, timeline);
1243
1303
  return timeline;
1244
1304
  }
@@ -1251,7 +1311,7 @@ var MOTION_DRAW_EFFECT_ID = "~draw";
1251
1311
  var TRANSFORM_PROPS = ["x", "y", "rotation", "scaleX", "scaleY", "follow"];
1252
1312
  var tidy = (v) => Math.round(v * 1e6) / 1e6 || 0;
1253
1313
  function applyMotionControlValues(scene, values) {
1254
- if (!values || !_optionalChain([scene, 'access', _36 => _36.controls, 'optionalAccess', _37 => _37.length])) return scene;
1314
+ if (!values || !_optionalChain([scene, 'access', _44 => _44.controls, 'optionalAccess', _45 => _45.length])) return scene;
1255
1315
  const map = /* @__PURE__ */ new Map();
1256
1316
  for (const control of scene.controls) {
1257
1317
  const value = values[control.name];
@@ -1297,7 +1357,7 @@ function shapeIn(entry, shape) {
1297
1357
  }
1298
1358
  function shapeAt(entry, timeline, nodes, time) {
1299
1359
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "shape"));
1300
- if (!_optionalChain([track, 'optionalAccess', _38 => _38.length])) return null;
1360
+ if (!_optionalChain([track, 'optionalAccess', _46 => _46.length])) return null;
1301
1361
  const s = activeSegment(track, time);
1302
1362
  const fromId = String(s ? s.from : track[0].from);
1303
1363
  if (!s) return fromId === entry.node.id ? null : nodes.get(fromId) ? shapeIn(entry, nodes.get(fromId)) : null;
@@ -1320,8 +1380,8 @@ function motionFollowRoute(entry, path) {
1320
1380
  }
1321
1381
  function followAt(entry, timeline, nodes, time) {
1322
1382
  const track = timeline.tracks.get(motionTrackKey(entry.node.id, "follow"));
1323
- const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _39 => _39[0]])));
1324
- if (!_optionalChain([s, 'optionalAccess', _40 => _40.path])) return null;
1383
+ const s = _nullishCoalesce(activeSegment(track, time), () => ( _optionalChain([track, 'optionalAccess', _47 => _47[0]])));
1384
+ if (!_optionalChain([s, 'optionalAccess', _48 => _48.path])) return null;
1325
1385
  const route = motionFollowRoute(entry, nodes.get(s.path));
1326
1386
  const progress = Number(trackValue(track, time));
1327
1387
  if (!route || !(progress >= 0)) return null;
@@ -1336,9 +1396,11 @@ function followAt(entry, timeline, nodes, time) {
1336
1396
  function motionNodeOffset(entry, timeline, time, nodes = indexMotionNodes([entry.node]), raw = false, follow = true) {
1337
1397
  const read = (p, rest) => {
1338
1398
  const v = trackValue(timeline.tracks.get(motionTrackKey(entry.node.id, p)), time, raw);
1339
- return withMotionLayers(timeline, entry.node.id, p, typeof v === "number" ? v : rest, time, raw);
1399
+ const layered = withMotionLayers(timeline, entry.node.id, p, typeof v === "number" ? v : rest, time, raw);
1400
+ return p === "follow" ? layered : applyMotionDrivers(timeline, entry.node.id, p, layered, time, raw);
1340
1401
  };
1341
- if (!TRANSFORM_PROPS.some((p) => (follow || p !== "follow") && (timeline.tracks.has(motionTrackKey(entry.node.id, p)) || timeline.tracks.has(motionAddTrackKey(entry.node.id, p))))) return null;
1402
+ const driven = _optionalChain([timeline, 'access', _49 => _49.drivers, 'optionalAccess', _50 => _50.some, 'call', _51 => _51((d) => d.target === entry.node.id && d.property !== "opacity")]);
1403
+ if (!driven && !TRANSFORM_PROPS.some((p) => (follow || p !== "follow") && (timeline.tracks.has(motionTrackKey(entry.node.id, p)) || timeline.tracks.has(motionAddTrackKey(entry.node.id, p))))) return null;
1342
1404
  const dx = read("x", 0);
1343
1405
  const dy = read("y", 0);
1344
1406
  const rotation = read("rotation", 0);
@@ -1364,7 +1426,7 @@ function motionFrameAt(scene, time, options = {}) {
1364
1426
  const nodes = indexMotionNodes(s.artwork.nodes);
1365
1427
  const drawOn = options.draw !== false;
1366
1428
  const value = (id, p) => trackValue(timeline.tracks.get(motionTrackKey(id, p)), time);
1367
- const animated = new Set(timeline.segments.map((seg) => seg.node));
1429
+ const animated = /* @__PURE__ */ new Set([...timeline.segments.map((seg) => seg.node), ...motionDrivenNodes(timeline)]);
1368
1430
  const clips = {};
1369
1431
  const visit = (node) => {
1370
1432
  let out = node.type === "group" ? { ...node, children: node.children.map(visit) } : node;
@@ -1381,7 +1443,7 @@ function motionFrameAt(scene, time, options = {}) {
1381
1443
  if (out.visible === void 0) delete out.visible;
1382
1444
  }
1383
1445
  const effectTracks = [...timeline.tracks.keys()].filter((k) => k.startsWith(`${id}\0effect:`));
1384
- if (effectTracks.length && _optionalChain([out, 'access', _41 => _41.effects, 'optionalAccess', _42 => _42.length])) {
1446
+ if (effectTracks.length && _optionalChain([out, 'access', _52 => _52.effects, 'optionalAccess', _53 => _53.length])) {
1385
1447
  out = {
1386
1448
  ...out,
1387
1449
  effects: out.effects.map((effect) => {
@@ -1421,7 +1483,7 @@ function motionFrameAt(scene, time, options = {}) {
1421
1483
  return p;
1422
1484
  });
1423
1485
  }
1424
- const opacity = value(id, "opacity");
1486
+ const opacity = _optionalChain([timeline, 'access', _54 => _54.drivers, 'optionalAccess', _55 => _55.some, 'call', _56 => _56((d) => d.target === id && d.property === "opacity")]) ? motionPropertyValue(timeline, id, "opacity", time) : value(id, "opacity");
1425
1487
  if (typeof opacity === "number") {
1426
1488
  const o = Math.min(1, Math.max(0, tidy(opacity)));
1427
1489
  out = { ...out, opacity: o };
@@ -1494,7 +1556,7 @@ function motionPath(scene, id, options = {}) {
1494
1556
  const r = (n) => Math.round(n * 100) / 100;
1495
1557
  const centreAt = (t) => {
1496
1558
  const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
1497
- const b = _optionalChain([node, 'optionalAccess', _43 => _43.bounds]);
1559
+ const b = _optionalChain([node, 'optionalAccess', _57 => _57.bounds]);
1498
1560
  return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
1499
1561
  };
1500
1562
  const points = [];
@@ -1517,12 +1579,49 @@ function motionPathsSvg(paths, camera, color = "#f43f5e") {
1517
1579
  (p) => `<g data-motion-path="${p.id.replace(/"/g, "&quot;")}" fill="none" stroke="${color}" stroke-width="${f(w)}" stroke-linecap="round" stroke-linejoin="round" opacity="0.8"><polyline points="${p.points.map((q) => `${f(q.x)},${f(q.y)}`).join(" ")}" stroke-dasharray="${f(w * 3)} ${f(w * 2)}"/>${p.keys.map((k) => `<circle cx="${f(k.x)}" cy="${f(k.y)}" r="${f(w * 2.5)}" fill="${color}" stroke="none"/>`).join("")}</g>`
1518
1580
  ).join("");
1519
1581
  }
1582
+ function motionBlurTimes(scene, time, blur = {}) {
1583
+ const shutter = Math.min(720, Math.max(0, _nullishCoalesce(blur.shutter, () => ( 180))));
1584
+ const most = Math.max(1, Math.min(32, Math.floor(_nullishCoalesce(blur.samples, () => ( 8)))));
1585
+ const span = 1e3 / (_nullishCoalesce(scene.fps, () => ( 30))) * shutter / 360;
1586
+ if (span <= 0 || most <= 1) return [time];
1587
+ const ids = motionMovingIds(scene);
1588
+ if (!ids.length) return [time];
1589
+ const a = indexMotionNodes(motionFrameAt(scene, time - span / 2).document.nodes);
1590
+ const b = indexMotionNodes(motionFrameAt(scene, time + span / 2).document.nodes);
1591
+ let travel = 0;
1592
+ for (const id of ids) {
1593
+ const p = _optionalChain([a, 'access', _58 => _58.get, 'call', _59 => _59(id), 'optionalAccess', _60 => _60.bounds]);
1594
+ const q = _optionalChain([b, 'access', _61 => _61.get, 'call', _62 => _62(id), 'optionalAccess', _63 => _63.bounds]);
1595
+ if (p && q) travel = Math.max(travel, Math.abs(p.x - q.x), Math.abs(p.y - q.y), Math.abs(p.x + p.width - q.x - q.width), Math.abs(p.y + p.height - q.y - q.height));
1596
+ }
1597
+ const n = Math.min(most, Math.ceil(travel / 1.5));
1598
+ if (n <= 1) return [time];
1599
+ return Array.from({ length: n }, (_, k) => time - span / 2 + span * (k + 0.5) / n);
1600
+ }
1601
+ function averagedFrames(scene, times, options, prefix) {
1602
+ const n = times.length;
1603
+ return `<g data-motion-blur="${n}" style="isolation:isolate">${times.map((t, k) => {
1604
+ const own = `${prefix}blur${k}-`;
1605
+ const frame = motionFrameToSvg(scene, t, { ...options, idPrefix: own, background: false });
1606
+ const view = _nullishCoalesce(_optionalChain([/viewBox="([^"]*)"/, 'access', _64 => _64.exec, 'call', _65 => _65(frame), 'optionalAccess', _66 => _66[1]]), () => ( ""));
1607
+ const inner = k === n - 1 ? innerSvg(frame) : innerSvg(frame).replace(new RegExp(` id="(?!${own.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
1608
+ return `<svg viewBox="${view}" preserveAspectRatio="none" style="mix-blend-mode:plus-lighter" opacity="${Math.round(1e4 / n) / 1e4}">${inner}</svg>`;
1609
+ }).join("")}</g>`;
1610
+ }
1520
1611
  var innerSvg = (svg) => svg.slice(svg.indexOf(">") + 1, svg.lastIndexOf("</svg>"));
1521
1612
  function motionStageSvg(scene, time, options = {}) {
1522
- const { onionSkin, motionPaths, motionPathColor, ...svgOptions } = options;
1523
- const current = motionFrameToSvg(scene, time, svgOptions);
1524
- if (!onionSkin && !motionPaths) return current;
1613
+ const { onionSkin, motionPaths, motionPathColor, motionBlur, ...svgOptions } = options;
1525
1614
  const prefix = _nullishCoalesce(svgOptions.idPrefix, () => ( "vg-"));
1615
+ let current = motionFrameToSvg(scene, time, svgOptions);
1616
+ const blur = motionBlur === false ? void 0 : _nullishCoalesce(motionBlur, () => ( scene.motionBlur));
1617
+ const blurTimes = blur ? motionBlurTimes(scene, time, blur) : [time];
1618
+ if (blurTimes.length > 1) {
1619
+ const open2 = current.indexOf(">") + 1;
1620
+ const body2 = current.slice(open2, current.lastIndexOf("</svg>"));
1621
+ const bg2 = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _67 => _67.exec, 'call', _68 => _68(body2), 'optionalAccess', _69 => _69[0]]), () => ( "")) : "";
1622
+ current = `${current.slice(0, open2)}${bg2}${averagedFrames(scene, blurTimes, svgOptions, prefix)}</svg>`;
1623
+ }
1624
+ if (!onionSkin && !motionPaths) return current;
1526
1625
  const ghosts = onionSkin ? motionGhosts(scene, time, onionSkin).map((g, i) => {
1527
1626
  const own = `${prefix}ghost${i}-`;
1528
1627
  const inner = innerSvg(motionFrameToSvg(scene, g.time, { ...svgOptions, idPrefix: own, background: false })).replace(new RegExp(` id="(?!${own.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
@@ -1534,7 +1633,7 @@ function motionStageSvg(scene, time, options = {}) {
1534
1633
  const open = current.indexOf(">") + 1;
1535
1634
  const close = current.lastIndexOf("</svg>");
1536
1635
  const body = current.slice(open, close);
1537
- const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _44 => _44.exec, 'call', _45 => _45(body), 'optionalAccess', _46 => _46[0]]), () => ( "")) : "";
1636
+ const bg = svgOptions.background !== false && scene.artwork.background ? _nullishCoalesce(_optionalChain([/^\s*<rect[^>]*\/>/, 'access', _70 => _70.exec, 'call', _71 => _71(body), 'optionalAccess', _72 => _72[0]]), () => ( "")) : "";
1538
1637
  return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
1539
1638
  }
1540
1639
  function motionContactSheetSvg(scene, times, options = {}) {
@@ -1553,7 +1652,7 @@ function motionContactSheetSvg(scene, times, options = {}) {
1553
1652
  const x = gap + i % cols * (w + gap);
1554
1653
  const y = gap + Math.floor(i / cols) * (h + label + gap);
1555
1654
  const frame = motionStageSvg(scene, t, { ...frameOptions, idPrefix: `${prefix}f${i}-` });
1556
- const view = _nullishCoalesce(_optionalChain([/viewBox="([^"]*)"/, 'access', _47 => _47.exec, 'call', _48 => _48(frame), 'optionalAccess', _49 => _49[1]]), () => ( `0 0 ${w} ${h}`));
1655
+ const view = _nullishCoalesce(_optionalChain([/viewBox="([^"]*)"/, 'access', _73 => _73.exec, 'call', _74 => _74(frame), 'optionalAccess', _75 => _75[1]]), () => ( `0 0 ${w} ${h}`));
1557
1656
  const inner = innerSvg(frame).replace(new RegExp(` id="(?!${`${prefix}f${i}-`.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
1558
1657
  const shot = _nullishCoalesce(timeline.shots.find((s) => t >= s.start && t < s.end), () => ( timeline.shots[timeline.shots.length - 1]));
1559
1658
  const caption = labels ? `<text x="${x + w / 2}" y="${y + h + label * 0.8}" font-family="system-ui, sans-serif" font-size="${Math.round(label * 0.7)}" text-anchor="middle" fill="#64748b">${(t / 1e3).toFixed(2)} s${shot ? ` \xB7 ${shot.id.replace(/[<&]/g, "")}` : ""}</text>` : "";
@@ -1565,7 +1664,7 @@ function motionContactSheetSvg(scene, times, options = {}) {
1565
1664
  // src/animation-editor/check.ts
1566
1665
  var MOTION_MIN_CHANGE_MS = 120;
1567
1666
  var NUMERIC_PARAMS = (node, effectId) => {
1568
- const effect = _optionalChain([node, 'access', _50 => _50.effects, 'optionalAccess', _51 => _51.find, 'call', _52 => _52((e) => e.id === effectId)]);
1667
+ const effect = _optionalChain([node, 'access', _76 => _76.effects, 'optionalAccess', _77 => _77.find, 'call', _78 => _78((e) => e.id === effectId)]);
1569
1668
  if (!effect) return null;
1570
1669
  return Object.entries(effect.params).filter(([, v]) => typeof v === "number").map(([k]) => k);
1571
1670
  };
@@ -1646,7 +1745,7 @@ function checkMotionScene(scene) {
1646
1745
  if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
1647
1746
  issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
1648
1747
  if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
1649
- if (command2.type === "play" && !_optionalChain([scene, 'access', _53 => _53.clips, 'optionalAccess', _54 => _54[command2.clip]]))
1748
+ if (command2.type === "play" && !_optionalChain([scene, 'access', _79 => _79.clips, 'optionalAccess', _80 => _80[command2.clip]]))
1650
1749
  issues.push(motionIssue("unknown-clip", `There is no clip ${namedWithSuggestion(command2.clip, Object.keys(_nullishCoalesce(scene.clips, () => ( {}))))}; define it with set_animation_clip.`, where));
1651
1750
  if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
1652
1751
  const into = nodes.get(command2.into);
@@ -1720,6 +1819,28 @@ function checkMotionScene(scene) {
1720
1819
  }
1721
1820
  });
1722
1821
  });
1822
+ const drivers = _nullishCoalesce(scene.drivers, () => ( []));
1823
+ drivers.forEach((d, i) => {
1824
+ for (const id of [d.target, d.source.node])
1825
+ if (!nodes.has(id)) issues.push(motionIssue("unknown-target", `Driver ${i + 1} names ${namedWithSuggestion(id, [...nodes.keys()])}, which the artwork does not have.`, { ids: [id] }));
1826
+ });
1827
+ {
1828
+ const key = (n, p) => `${n}.${p}`;
1829
+ const edges = /* @__PURE__ */ new Map();
1830
+ for (const d of drivers) edges.set(key(d.target, d.property), [..._nullishCoalesce(edges.get(key(d.target, d.property)), () => ( [])), key(d.source.node, d.source.property)]);
1831
+ const state = /* @__PURE__ */ new Map();
1832
+ const loop = [];
1833
+ const visit = (k, trail) => {
1834
+ if (state.get(k) === 2) return false;
1835
+ if (state.get(k) === 1) return loop.push(...trail.slice(trail.indexOf(k)), k), true;
1836
+ state.set(k, 1);
1837
+ for (const n of _nullishCoalesce(edges.get(k), () => ( []))) if (visit(n, [...trail, k])) return true;
1838
+ state.set(k, 2);
1839
+ return false;
1840
+ };
1841
+ for (const k of edges.keys()) if (visit(k, [])) break;
1842
+ if (loop.length) issues.push(motionIssue("driver-cycle", `Drivers follow each other round in a loop (${loop.join(" \u2190 ")}); remove one of them.`, { ids: [...new Set(loop.map((k) => k.split(".")[0]))] }));
1843
+ }
1723
1844
  for (const [name, clip] of Object.entries(_nullishCoalesce(scene.clips, () => ( {}))))
1724
1845
  for (const command2 of clip.run)
1725
1846
  if (command2.type !== "wait") {
@@ -1739,7 +1860,7 @@ function checkMotionScene(scene) {
1739
1860
  if (!shot.at) return;
1740
1861
  const timing = timeline.shots[i];
1741
1862
  if (timing.cue === void 0) {
1742
- const what = shot.at.beat !== void 0 ? `beat ${shot.at.beat}, but the scene has no beat grid; set one with set_animation_settings ({ "beat": { "bpm": \u2026 } })` : `word ${shot.at.word}, but the narration has ${_nullishCoalesce(_optionalChain([scene, 'access', _55 => _55.narration, 'optionalAccess', _56 => _56.words, 'access', _57 => _57.length]), () => ( 0))} words (indexes from 0)`;
1863
+ 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', _81 => _81.narration, 'optionalAccess', _82 => _82.words, 'access', _83 => _83.length]), () => ( 0))} words (indexes from 0)`;
1743
1864
  issues.push(motionIssue("unknown-cue", `Shot "${shot.id}" is cued to ${what}.`, { shot: shot.id }));
1744
1865
  } else if (timing.missedBy !== void 0) {
1745
1866
  const late = Math.round(timing.missedBy);
@@ -1772,7 +1893,7 @@ function checkMotionScene(scene) {
1772
1893
  const cam = frame.camera;
1773
1894
  const docIndex = indexMotionNodes(frame.document.nodes);
1774
1895
  for (const id of command2.targets) {
1775
- const b = _optionalChain([docIndex, 'access', _58 => _58.get, 'call', _59 => _59(id), 'optionalAccess', _60 => _60.bounds]);
1896
+ const b = _optionalChain([docIndex, 'access', _84 => _84.get, 'call', _85 => _85(id), 'optionalAccess', _86 => _86.bounds]);
1776
1897
  if (!b || reported.has(id) || !visibleAt(timeline, id, timing.end, nodes.get(id).node)) continue;
1777
1898
  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;
1778
1899
  if (outside) {
@@ -1810,6 +1931,7 @@ var MOTION_EDIT_OPS = [
1810
1931
  "set_artwork",
1811
1932
  "set_controls",
1812
1933
  "set_clip",
1934
+ "set_drivers",
1813
1935
  "replace_scene"
1814
1936
  ];
1815
1937
  var EditError = class extends Error {
@@ -1854,18 +1976,25 @@ function applyOne(scene, edit) {
1854
1976
  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.');
1855
1977
  next.steps = edit.steps;
1856
1978
  }
1857
- for (const key of ["beat", "narration"]) {
1979
+ for (const key of ["beat", "narration", "motionBlur"]) {
1858
1980
  if (edit[key] === null) delete next[key];
1859
1981
  else if (edit[key] !== void 0) next[key] = edit[key];
1860
1982
  }
1861
1983
  {
1862
- const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"/.test(p));
1984
+ const problems = motionSceneShapeProblems(next).filter((p) => /"beat"|"narration"|"motionBlur"/.test(p));
1863
1985
  if (problems.length) fail(problems.join(" "));
1864
1986
  }
1865
1987
  if (edit.target === null) delete next.target;
1866
1988
  else if (edit.target !== void 0) next.target = edit.target;
1867
1989
  return next;
1868
1990
  }
1991
+ case "set_drivers": {
1992
+ const problems = driversShapeProblems(edit.drivers);
1993
+ if (problems.length) fail(problems.join(" "));
1994
+ const next = { ...scene, drivers: edit.drivers };
1995
+ if (!edit.drivers.length) delete next.drivers;
1996
+ return next;
1997
+ }
1869
1998
  case "set_clip": {
1870
1999
  if (typeof edit.name !== "string" || !edit.name) fail('Give the clip a "name".');
1871
2000
  const clips = { ..._nullishCoalesce(scene.clips, () => ( {})) };
@@ -1996,9 +2125,9 @@ function applyMotionEdits(scene, edits) {
1996
2125
  let next = scene;
1997
2126
  for (let i = 0; i < list.length; i++) {
1998
2127
  const edit = list[i];
1999
- const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _61 => _61.op])}): ` : "";
2128
+ const prefix = list.length > 1 ? `op ${i + 1} (${_optionalChain([edit, 'optionalAccess', _87 => _87.op])}): ` : "";
2000
2129
  if (!edit || !MOTION_EDIT_OPS.includes(edit.op))
2001
- return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _62 => _62.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
2130
+ return { ok: false, error: `${prefix}Unknown op ${namedWithSuggestion(String(_optionalChain([edit, 'optionalAccess', _88 => _88.op])), MOTION_EDIT_OPS)}. Nothing was changed.` };
2002
2131
  try {
2003
2132
  next = applyOne(next, edit);
2004
2133
  } catch (error) {
@@ -2020,7 +2149,7 @@ function resolveMotionEditRefs(scene, edits) {
2020
2149
  const resolved = [];
2021
2150
  let next = scene;
2022
2151
  for (let i = 0; i < edits.length; i++) {
2023
- const where = `op ${i + 1} (${_optionalChain([edits, 'access', _63 => _63[i], 'optionalAccess', _64 => _64.op])})`;
2152
+ const where = `op ${i + 1} (${_optionalChain([edits, 'access', _89 => _89[i], 'optionalAccess', _90 => _90.op])})`;
2024
2153
  let problem = null;
2025
2154
  const walk = (v) => {
2026
2155
  if (typeof v === "string") {
@@ -2053,9 +2182,9 @@ function resolveMotionEditRefs(scene, edits) {
2053
2182
  resolved.push(edit);
2054
2183
  const out = { op: edit.op };
2055
2184
  if (edit.op === "insert_shots") out.shots = edit.shots.map((s) => s.id);
2056
- if ("shot" in edit && typeof edit.shot === "string") out.shot = edit.op === "update_shot" && _optionalChain([edit, 'access', _65 => _65.set, 'optionalAccess', _66 => _66.id]) ? edit.set.id : edit.shot;
2185
+ if ("shot" in edit && typeof edit.shot === "string") out.shot = edit.op === "update_shot" && _optionalChain([edit, 'access', _91 => _91.set, 'optionalAccess', _92 => _92.id]) ? edit.set.id : edit.shot;
2057
2186
  if (edit.op === "add_commands") {
2058
- const at = _nullishCoalesce(_nullishCoalesce(edit.at, () => ( _optionalChain([before, 'access', _67 => _67.shots, 'access', _68 => _68.find, 'call', _69 => _69((s) => s.id === edit.shot), 'optionalAccess', _70 => _70.run, 'access', _71 => _71.length]))), () => ( 0));
2187
+ const at = _nullishCoalesce(_nullishCoalesce(edit.at, () => ( _optionalChain([before, 'access', _93 => _93.shots, 'access', _94 => _94.find, 'call', _95 => _95((s) => s.id === edit.shot), 'optionalAccess', _96 => _96.run, 'access', _97 => _97.length]))), () => ( 0));
2059
2188
  out.indexes = edit.commands.map((_, k) => at + k);
2060
2189
  }
2061
2190
  if (edit.op === "move_command") out.indexes = [edit.to];
@@ -2167,7 +2296,13 @@ function toFlairSvgMotion(scene, options = {}) {
2167
2296
  if (!entry) return void 0;
2168
2297
  return [...entry.ancestors, path.id].find((id) => timeline.tracks.has(motionTrackKey(id, p)));
2169
2298
  };
2170
- const animatedNodes = new Set(timeline.segments.map((s) => s.node).filter(Boolean));
2299
+ const animatedNodes = /* @__PURE__ */ new Set([...timeline.segments.map((s) => s.node).filter(Boolean), ...motionDrivenNodes(timeline)]);
2300
+ const drivenBy = /* @__PURE__ */ new WeakSet();
2301
+ const sourcesOf = (id, props) => motionDriverSources(timeline, id, props).map((s) => {
2302
+ const copy = { ...s };
2303
+ drivenBy.add(copy);
2304
+ return copy;
2305
+ });
2171
2306
  const pathsSeen = /* @__PURE__ */ new Set();
2172
2307
  const wrap = {};
2173
2308
  const routes = [];
@@ -2208,16 +2343,17 @@ function toFlairSvgMotion(scene, options = {}) {
2208
2343
  const wrapper = freshId(`${id}--follow`);
2209
2344
  const centre = freshId(`${id}--follow-centre`);
2210
2345
  const c = entry.center;
2211
- const at = _optionalChain([motionNodeOffset, 'call', _72 => _72(entry, timeline, 0, nodes), 'optionalAccess', _73 => _73.follow]);
2346
+ const at = _optionalChain([motionNodeOffset, 'call', _98 => _98(entry, timeline, 0, nodes), 'optionalAccess', _99 => _99.follow]);
2212
2347
  const placed = at ? `translate(${f(at.x)} ${f(at.y)})${at.angle ? ` rotate(${f(at.angle)})` : ""}` : void 0;
2213
2348
  (wrap[id] ??= []).push({ id: wrapper, ...placed ? { transform: placed } : {} }, { id: centre, transform: `translate(${f(-c.x)} ${f(-c.y)})` });
2214
2349
  follows.push({ id, wrapper, routes: ids, segments: followSegments.filter((seg) => seg.path && ids.has(seg.path)) });
2215
2350
  }
2216
2351
  }
2217
- if (timeline.tracks.has(motionTrackKey(id, "opacity")))
2218
- channels.push({ id, kind: "attr", attr: "opacity", segments: [...track(id, "opacity")], exact: plays, value: (t) => round2(Math.min(1, Math.max(0, Number(trackValue(track(id, "opacity"), t))))) });
2352
+ const opacitySources = sourcesOf(id, ["opacity"]);
2353
+ if (timeline.tracks.has(motionTrackKey(id, "opacity")) || opacitySources.length)
2354
+ channels.push({ id, kind: "attr", attr: "opacity", segments: [...track(id, "opacity"), ...opacitySources], exact: (s) => plays(s) && !drivenBy.has(s), value: (t) => round2(motionPropertyValue(timeline, id, "opacity", t)) });
2219
2355
  const followed = follows.some((x) => x.id === id);
2220
- const transformSegments = TRANSFORM_PROPS2.flatMap((p) => followed && p === "follow" ? [] : [...track(id, p), ..._nullishCoalesce(timeline.tracks.get(motionAddTrackKey(id, p)), () => ( []))]);
2356
+ const transformSegments = [...TRANSFORM_PROPS2.flatMap((p) => followed && p === "follow" ? [] : [...track(id, p), ..._nullishCoalesce(timeline.tracks.get(motionAddTrackKey(id, p)), () => ( []))]), ...sourcesOf(id, ["x", "y", "rotation", "scaleX", "scaleY"])];
2221
2357
  if (transformSegments.length) {
2222
2358
  const base = node.transform ? ` matrix(${node.transform.map(f).join(" ")})` : "";
2223
2359
  const c = entry.center;
@@ -2230,7 +2366,7 @@ function toFlairSvgMotion(scene, options = {}) {
2230
2366
  kind: "attr",
2231
2367
  attr: "transform",
2232
2368
  segments: transformSegments,
2233
- exact: (s) => LINEAR_TRANSFORM.has(s.property) && plays(s),
2369
+ exact: (s) => LINEAR_TRANSFORM.has(s.property) && plays(s) && !drivenBy.has(s),
2234
2370
  overshoot: true,
2235
2371
  // x, y and the angle are the transform's numbers 0, 1 and 2 (`MotionSegment.index`).
2236
2372
  value: (t) => transformAt(t, false),
@@ -2268,7 +2404,7 @@ function toFlairSvgMotion(scene, options = {}) {
2268
2404
  exact: () => false,
2269
2405
  value: (t) => {
2270
2406
  const frame = motionFrameAt(scene, t, { draw: false, offsets: false });
2271
- const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _74 => _74(frame.document.nodes), 'access', _75 => _75.get, 'call', _76 => _76(id), 'optionalAccess', _77 => _77.node]));
2407
+ const now = _chunkYJLCBA7Vcjs.expandVectorAppearance.call(void 0, _optionalChain([indexMotionNodes, 'call', _100 => _100(frame.document.nodes), 'access', _101 => _101.get, 'call', _102 => _102(id), 'optionalAccess', _103 => _103.node]));
2272
2408
  if (!now || now.type !== "path") return "";
2273
2409
  const bezier = _chunkO2UH7LZEcjs.transformBezierPath.call(void 0, _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, now, _chunkYJLCBA7Vcjs.IDENTITY_MATRIX, { effects: false }), inverse);
2274
2410
  return _chunkO2UH7LZEcjs.bezierPathToSvg.call(void 0, bezier, { precision: Math.min(precision, 2) });
@@ -2289,8 +2425,8 @@ function toFlairSvgMotion(scene, options = {}) {
2289
2425
  ...delay > 0 ? { delay: round2(delay) } : {},
2290
2426
  ms: round2(ms),
2291
2427
  ease: written,
2292
- ..._optionalChain([segment, 'optionalAccess', _78 => _78.behavior]) ? { behavior: segment.behavior } : {},
2293
- ..._optionalChain([segment, 'optionalAccess', _79 => _79.steps]) ? { strobe: segment.steps } : {}
2428
+ ..._optionalChain([segment, 'optionalAccess', _104 => _104.behavior]) ? { behavior: segment.behavior } : {},
2429
+ ..._optionalChain([segment, 'optionalAccess', _105 => _105.steps]) ? { strobe: segment.steps } : {}
2294
2430
  };
2295
2431
  if (channel.kind === "view") animate.push({ ...base, view: to });
2296
2432
  else if (channel.kind === "draw") animate.push({ ...base, draw: [from, to] });
@@ -2375,11 +2511,12 @@ function toFlairSvgMotion(scene, options = {}) {
2375
2511
  }
2376
2512
  animate.sort((x, y) => (_nullishCoalesce(x.step, () => ( 0))) - (_nullishCoalesce(y.step, () => ( 0))) || (_nullishCoalesce(x.delay, () => ( 0))) - (_nullishCoalesce(y.delay, () => ( 0))));
2377
2513
  }
2514
+ if (scene.motionBlur) notes.push("Motion blur is drawn on the stage only; Flair plays the scene sharp.");
2378
2515
  if (trigger && (_nullishCoalesce(scene.loop, () => ( "none"))) !== "none") notes.push(`The scene plays on ${trigger}, so its loop does not apply in Flair.`);
2379
2516
  let svg = _chunkYJLCBA7Vcjs.vectorDocumentToSvg.call(void 0, { ...scene.artwork, nodes: _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, scene.artwork.nodes) }, { pretty: false, precision, wrap });
2380
2517
  if (routes.length) svg = svg.replace(/^(<svg[^>]*>)/, `$1<defs>${routes.join("")}</defs>`);
2381
2518
  let motion = { svg, animate };
2382
- if (_optionalChain([scene, 'access', _80 => _80.controls, 'optionalAccess', _81 => _81.length])) {
2519
+ if (_optionalChain([scene, 'access', _106 => _106.controls, 'optionalAccess', _107 => _107.length])) {
2383
2520
  const declarations = scene.controls.map((c) => `{{${c.name}: color default=${c.default.toUpperCase()}${c.label ? ` label='${c.label.replace(/'/g, "\u2019")}'` : ""}}}`).join(" ");
2384
2521
  const swap = (text) => scene.controls.reduce((s, c) => s.replace(new RegExp(`${c.default}(?![0-9a-fA-F])`, "gi"), `{{${c.name}}}`), text);
2385
2522
  svg = svg.replace(/^(<svg[^>]*>)/, `$1<!-- ${declarations} -->`);
@@ -2401,7 +2538,7 @@ function toFlairVectorEffect(scene, options = {}) {
2401
2538
  tags: _nullishCoalesce(options.tags, () => ( [])),
2402
2539
  motion: out.motion,
2403
2540
  hold: out.hold,
2404
- ..._optionalChain([scene, 'access', _82 => _82.presets, 'optionalAccess', _83 => _83.length]) ? { presets: scene.presets } : {},
2541
+ ..._optionalChain([scene, 'access', _108 => _108.presets, 'optionalAccess', _109 => _109.length]) ? { presets: scene.presets } : {},
2405
2542
  ...out.trigger ? { trigger: out.trigger } : {}
2406
2543
  };
2407
2544
  }
@@ -2445,7 +2582,7 @@ function resolvePlaceholders(motion, notes) {
2445
2582
  const filled = text.replace(PLACEHOLDER, (_, name) => {
2446
2583
  const d = declared.get(name);
2447
2584
  if (!d) unknown.add(name);
2448
- return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _84 => _84.value]), () => ( ""))).replace(/"/g, '\\"');
2585
+ return (_nullishCoalesce(_optionalChain([d, 'optionalAccess', _110 => _110.value]), () => ( ""))).replace(/"/g, '\\"');
2449
2586
  });
2450
2587
  for (const name of unknown) notes.push(`Placeholder "{{${name}}}" has no declaration, so it was left empty.`);
2451
2588
  return { motion: JSON.parse(filled), controls };
@@ -2499,7 +2636,7 @@ function commandsFor(anim, nodes, entered, notes) {
2499
2636
  if (anim.along) {
2500
2637
  const path = nodes.get(anim.along);
2501
2638
  if (!path || path.node.type !== "path") notes.push(`${where} follows "${anim.along}", which is not a path in the artwork; the follow was skipped.`);
2502
- else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _85 => _85.at, 'optionalAccess', _86 => _86[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _87 => _87.at, 'optionalAccess', _88 => _88[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2639
+ else out.push({ type: "follow", targets, path: anim.along, from: _nullishCoalesce(_optionalChain([anim, 'access', _111 => _111.at, 'optionalAccess', _112 => _112[0]]), () => ( 0)), to: _nullishCoalesce(_optionalChain([anim, 'access', _113 => _113.at, 'optionalAccess', _114 => _114[1]]), () => ( 1)), ...anim.orient ? { rotate: true } : {}, ...timing, ...ease(MOTION_EASE_IN_OUT) });
2503
2640
  }
2504
2641
  for (const [attr, [a, b]] of Object.entries(_nullishCoalesce(anim.attrs, () => ( {})))) {
2505
2642
  if (attr === "opacity") {
@@ -2510,7 +2647,7 @@ function commandsFor(anim, nodes, entered, notes) {
2510
2647
  out.push({ type: "enter", targets, style: "fade", ...timing, ...ease(MOTION_EASE_OUT) });
2511
2648
  } else out.push({ type: "fade", targets, to: Math.min(1, Math.max(0, to)), ...timing, ...ease(MOTION_EASE_IN_OUT) });
2512
2649
  } else if (attr === "fill" || attr === "stroke") {
2513
- if (_optionalChain([behavior, 'optionalAccess', _89 => _89.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2650
+ if (_optionalChain([behavior, 'optionalAccess', _115 => _115.type]) === "pulse") out.push({ type: "color-pulse", targets, color: String(b), paint: attr, count: _nullishCoalesce(behavior.count, () => ( 1)), ...timing, ...ease("linear") });
2514
2651
  else {
2515
2652
  if (behavior) notes.push(`${where}: a ${attr} ${behavior.type} became a plain colour change.`);
2516
2653
  out.push({ type: "style", targets, [attr]: String(b), ...timing, ...ease(MOTION_EASE_IN_OUT) });
@@ -2691,6 +2828,7 @@ function toLottie(scene, options = {}) {
2691
2828
  const timeline = compileMotionScene(scene);
2692
2829
  const nodes = indexMotionNodes(scene.artwork.nodes);
2693
2830
  const notes = [];
2831
+ if (scene.motionBlur) notes.push("Motion blur is drawn on the stage only; the Lottie plays sharp.");
2694
2832
  if (scene.trigger) notes.push(`The scene plays on ${scene.trigger}; Lottie has no trigger, so it plays through (markers show where each shot starts, for a player to stop at).`);
2695
2833
  let sampled = 0;
2696
2834
  const track = (id, p) => _nullishCoalesce(timeline.tracks.get(motionTrackKey(id, p)), () => ( []));
@@ -2762,16 +2900,22 @@ function toLottie(scene, options = {}) {
2762
2900
  const tr = STATIC_TR();
2763
2901
  const c = [entry.center.x, entry.center.y].map(round2);
2764
2902
  const own = (p) => track(id, p);
2765
- const layered = ["x", "y", "rotation", "scaleX", "scaleY"].some((p) => layers(id, p).length);
2903
+ const sources = motionDriverSources(timeline, id, ["x", "y", "rotation", "scaleX", "scaleY"]);
2904
+ const layered = ["x", "y", "rotation", "scaleX", "scaleY"].some((p) => layers(id, p).length) || sources.length > 0;
2766
2905
  const moving = ["x", "y", "rotation", "scaleX", "scaleY", "follow"].some((p) => own(p).length) || layered;
2767
2906
  if (moving) {
2768
2907
  const mixed = own("follow").length > 0 || layered;
2769
2908
  tr.a = { a: 0, k: c };
2770
- 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);
2771
- tr.r = animate(mixed ? [...own("rotation"), ...own("follow"), ...layers(id, "rotation")] : own("rotation"), (t) => [poseAt(entry, t).r], !mixed);
2772
- tr.s = animate(mixed ? [...own("scaleX"), ...own("scaleY"), ...layers(id, "scaleX"), ...layers(id, "scaleY")] : [...own("scaleX"), ...own("scaleY")], (t) => poseAt(entry, t).s, !mixed);
2909
+ tr.p = animate(mixed ? [...own("x"), ...own("y"), ...own("follow"), ...layers(id, "x"), ...layers(id, "y"), ...sources] : [...own("x"), ...own("y")], (t) => poseAt(entry, t).p, !mixed);
2910
+ tr.r = animate(mixed ? [...own("rotation"), ...own("follow"), ...layers(id, "rotation"), ...sources] : own("rotation"), (t) => [poseAt(entry, t).r], !mixed);
2911
+ tr.s = animate(mixed ? [...own("scaleX"), ...own("scaleY"), ...layers(id, "scaleX"), ...layers(id, "scaleY"), ...sources] : [...own("scaleX"), ...own("scaleY")], (t) => poseAt(entry, t).s, !mixed);
2773
2912
  if (tr.p.a === 0) tr.p = { a: 0, k: poseAt(entry, 0).p.map(round2) };
2774
2913
  }
2914
+ const opacitySources = motionDriverSources(timeline, id, ["opacity"]);
2915
+ if (opacitySources.length) {
2916
+ tr.o = animate([...own("opacity"), ...opacitySources], (t) => [motionPropertyValue(timeline, id, "opacity", t) * 100], false);
2917
+ return tr;
2918
+ }
2775
2919
  const opacity = own("opacity");
2776
2920
  const rest = _nullishCoalesce(entry.node.opacity, () => ( 1));
2777
2921
  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) };
@@ -2793,7 +2937,7 @@ function toLottie(scene, options = {}) {
2793
2937
  const shapeSegments = [...timeline.tracks.entries()].filter(([k]) => k.startsWith(`${path.id}\0`) && (k.endsWith("\0shape") || k.includes("\0effect:"))).flatMap(([, v]) => v);
2794
2938
  const geometryAt = (t) => {
2795
2939
  if (!shapeSegments.length) return _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2796
- const now = _optionalChain([indexMotionNodes, 'call', _90 => _90(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _91 => _91.get, 'call', _92 => _92(path.id), 'optionalAccess', _93 => _93.node]);
2940
+ const now = _optionalChain([indexMotionNodes, 'call', _116 => _116(motionFrameAt(scene, t, { draw: false, offsets: false }).document.nodes), 'access', _117 => _117.get, 'call', _118 => _118(path.id), 'optionalAccess', _119 => _119.node]);
2797
2941
  return now && now.type === "path" ? _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, _chunkYJLCBA7Vcjs.expandVectorAppearances.call(void 0, [now])[0]) : _chunkYJLCBA7Vcjs.vectorPathWorldBezier.call(void 0, path);
2798
2942
  };
2799
2943
  const rest = lottieContours(_chunkO2UH7LZEcjs.toBezierPath.call(void 0, geometryAt(0)), round2);
@@ -2841,7 +2985,7 @@ function toLottie(scene, options = {}) {
2841
2985
  lj: path.strokeLinejoin === "round" ? 2 : path.strokeLinejoin === "bevel" ? 3 : 1,
2842
2986
  ml: 4
2843
2987
  });
2844
- if (_optionalChain([path, 'access', _94 => _94.strokeDasharray, 'optionalAccess', _95 => _95.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2988
+ if (_optionalChain([path, 'access', _120 => _120.strokeDasharray, 'optionalAccess', _121 => _121.length])) notes.push(`"${path.id}": its dashes are not exported; the stroke is solid.`);
2845
2989
  if (path.strokeAlign && path.strokeAlign !== "center") notes.push(`"${path.id}": its stroke is drawn centred (Lottie has no inside or outside strokes).`);
2846
2990
  }
2847
2991
  const fill = paintOf(path.fill, "fill", path.id);
@@ -2992,7 +3136,7 @@ var AnimationEditorController = (_class = class {
2992
3136
  time: p.time,
2993
3137
  playing: p.playing,
2994
3138
  loop: p.loop,
2995
- currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _96 => _96(timeline, p.time), 'optionalAccess', _97 => _97.id]), () => ( null)),
3139
+ currentShot: _nullishCoalesce(_optionalChain([motionShotAt, 'call', _122 => _122(timeline, p.time), 'optionalAccess', _123 => _123.id]), () => ( null)),
2996
3140
  selection: this.selection,
2997
3141
  readOnly: this.readOnly,
2998
3142
  error: this.error,
@@ -3031,7 +3175,7 @@ var AnimationEditorController = (_class = class {
3031
3175
  this.applying = false;
3032
3176
  }
3033
3177
  this.notify();
3034
- _optionalChain([this, 'access', _98 => _98.onChange, 'optionalCall', _99 => _99(scene)]);
3178
+ _optionalChain([this, 'access', _124 => _124.onChange, 'optionalCall', _125 => _125(scene)]);
3035
3179
  }
3036
3180
  commit(label, next) {
3037
3181
  const before = this.scene;
@@ -3351,6 +3495,10 @@ var SETTINGS = {
3351
3495
  },
3352
3496
  fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
3353
3497
  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).' },
3498
+ motionBlur: {
3499
+ anyOf: [object({ shutter: { type: "number", exclusiveMinimum: 0, maximum: 720, description: "Shutter angle in degrees of a frame (default 180; 360 is a whole frame, more blur)." }, samples: { type: "integer", minimum: 2, maximum: 32, description: "Most moments a frame averages (default 8)." } }), { type: "null" }],
3500
+ description: "Motion blur on the stage and in rendered frames (not exported to Flair or Lottie); null turns it off."
3501
+ },
3354
3502
  beat: {
3355
3503
  anyOf: [
3356
3504
  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"]),
@@ -3476,6 +3624,8 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3476
3624
  ...includeElements ? { elements: describeMotionElements(s) } : {},
3477
3625
  shots: describeMotionShots(s),
3478
3626
  ...s.clips ? { clips: Object.fromEntries(Object.entries(s.clips).map(([name, c]) => [name, { ...c.description ? { description: c.description } : {}, commands: c.run.length, length: Math.round(motionClipLayout(s, name, "$").length) }])) } : {},
3627
+ ...s.drivers ? { drivers: s.drivers } : {},
3628
+ ...s.motionBlur ? { motionBlur: s.motionBlur } : {},
3479
3629
  ...s.controls ? { controls: s.controls } : {},
3480
3630
  ...s.presets ? { presets: s.presets } : {},
3481
3631
  issues: issueSummary(controller.getState().issues),
@@ -3590,9 +3740,9 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3590
3740
  handler: ({ from = 0, to, limit = 200 }) => {
3591
3741
  const s = scene();
3592
3742
  const timeline = compileMotionScene(s);
3593
- const words = _nullishCoalesce(_optionalChain([s, 'access', _100 => _100.narration, 'optionalAccess', _101 => _101.words]), () => ( []));
3743
+ const words = _nullishCoalesce(_optionalChain([s, 'access', _126 => _126.narration, 'optionalAccess', _127 => _127.words]), () => ( []));
3594
3744
  const end = _nullishCoalesce(to, () => ( Math.max(timeline.duration, words.length ? _nullishCoalesce(words[words.length - 1].end, () => ( words[words.length - 1].start)) : 0)));
3595
- const shotAt = (ms) => _optionalChain([timeline, 'access', _102 => _102.shots, 'access', _103 => _103.find, 'call', _104 => _104((x) => ms >= x.start && ms < x.end), 'optionalAccess', _105 => _105.id]);
3745
+ const shotAt = (ms) => _optionalChain([timeline, 'access', _128 => _128.shots, 'access', _129 => _129.find, 'call', _130 => _130((x) => ms >= x.start && ms < x.end), 'optionalAccess', _131 => _131.id]);
3596
3746
  const beats = [];
3597
3747
  if (s.beat && s.beat.bpm > 0) {
3598
3748
  const first = Math.max(0, Math.ceil(motionBeatAt(s.beat, from).beat - 1e-9));
@@ -3734,6 +3884,36 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3734
3884
  return apply(edits, "Fit to length", () => report);
3735
3885
  }
3736
3886
  };
3887
+ const PROP = { type: "string", enum: ["x", "y", "rotation", "scaleX", "scaleY", "opacity"], description: "x / y (offset, document units), rotation (degrees), scaleX / scaleY or opacity (1 = rest)." };
3888
+ const setDrivers = {
3889
+ name: "set_animation_drivers",
3890
+ title: "Make properties follow others",
3891
+ description: "Replaces the drivers (empty clears them): each makes a target's property follow a source node's property, as a change from rest \u2014 target change = source change \xB7 factor + offset, clamped \u2014 added to offsets and turns, multiplied into scales and opacity, on top of the target's own commands. For parallax (x follows x \xB7 0.5), a wheel turning as it rolls (rotation follows x \xB7 360 / (2\u03C0\xB7radius)), a shadow fading as a ball rises (opacity follows y \xB7 0.005). One undo step; loops between drivers are refused by check_animation. Returns the timing and issues.",
3892
+ inputSchema: object(
3893
+ {
3894
+ drivers: {
3895
+ type: "array",
3896
+ maxItems: 64,
3897
+ items: object(
3898
+ {
3899
+ target: { type: "string", minLength: 1, description: "Element id that follows." },
3900
+ property: PROP,
3901
+ source: object({ node: { type: "string", minLength: 1, description: "Element id it follows." }, property: PROP }, ["node", "property"], "What it follows."),
3902
+ factor: { type: "number", description: "Multiplies the source's change (default 1)." },
3903
+ offset: { type: "number", description: "Added to the change (default 0)." },
3904
+ clamp: { type: "array", items: { type: "number" }, minItems: 2, maxItems: 2, description: "Limits the change to [min, max]." }
3905
+ },
3906
+ ["target", "property", "source"],
3907
+ "One property following another."
3908
+ ),
3909
+ description: "Every driver (the list replaces the current one)."
3910
+ }
3911
+ },
3912
+ ["drivers"]
3913
+ ),
3914
+ annotations: { title: "Make properties follow others", ...WRITE_IDEMPOTENT },
3915
+ handler: ({ drivers }) => apply({ op: "set_drivers", drivers }, "Set drivers")
3916
+ };
3737
3917
  const setClip = {
3738
3918
  name: "set_animation_clip",
3739
3919
  title: "Define a reusable clip",
@@ -3842,7 +4022,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3842
4022
  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"]),
3843
4023
  annotations: { title: "Add commands to a shot", ...WRITE },
3844
4024
  handler: ({ shot, commands, at }) => {
3845
- const before = _nullishCoalesce(_optionalChain([scene, 'call', _106 => _106(), 'access', _107 => _107.shots, 'access', _108 => _108.find, 'call', _109 => _109((s) => s.id === shot), 'optionalAccess', _110 => _110.run, 'access', _111 => _111.length]), () => ( 0));
4025
+ const before = _nullishCoalesce(_optionalChain([scene, 'call', _132 => _132(), 'access', _133 => _133.shots, 'access', _134 => _134.find, 'call', _135 => _135((s) => s.id === shot), 'optionalAccess', _136 => _136.run, 'access', _137 => _137.length]), () => ( 0));
3846
4026
  const first = _nullishCoalesce(at, () => ( before));
3847
4027
  return apply({ op: "add_commands", shot, commands, at }, "Add commands", (s) => {
3848
4028
  const shots = describeMotionShots(s);
@@ -3974,7 +4154,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3974
4154
  }
3975
4155
  };
3976
4156
  const reads = [getAnimation, getShot, findElements, getMotionPath, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
3977
- const writes = [setArtworkSvg, setClip, copyAnimation, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, moveCommand, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
4157
+ const writes = [setArtworkSvg, setDrivers, setClip, copyAnimation, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, moveCommand, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
3978
4158
  return {
3979
4159
  id: _nullishCoalesce(options.id, () => ( "animation")),
3980
4160
  namespace: options.namespace,
@@ -4007,6 +4187,8 @@ Commands
4007
4187
  - enter also reveals through a clip over the target's box: wipe (behind an edge travelling "direction", default right), iris (from the centre; "shape": circle or box) and split (from a line through the middle; "orientation": horizontal or vertical). exit wipe hides the same way. Reveals need a width and a height: use draw / undraw for a straight line.
4008
4188
  - draw draws strokes on to a fraction; move, rotate, scale and fade change the pose (about the centre); style blends solid colours and stroke width.
4009
4189
  - Drawing a group of strokes: "order": "sequence" draws them one after another (a signature, a diagram), "sortBy": "distance" from "origin" reveals outward, "pace": "speed" keeps one speed; draw with "comet": 0.2 sends a short stroke along a path and off the end.
4190
+ - Fast motion reads better with blur: set_animation_settings { "motionBlur": { "shutter": 180 } } (stage and rendered frames only).
4191
+ - Tie properties together with set_animation_drivers: parallax layers, a wheel that turns as it rolls, a shadow that fades as something rises. Drivers add to the target's own commands.
4010
4192
  - Reuse motion: set_animation_clip defines a clip (commands targeting "$"), and { "type": "play", "clip": name, "targets": [...] } plays it on any nodes (with stagger and speed). copy_animation gives other nodes one node's motion.
4011
4193
  - move takes "via" waypoints for a curved, smooth path that passes exactly through each point (steady speed by default); use it instead of chaining moves, which stop at each point.
4012
4194
  - enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
@@ -4035,7 +4217,7 @@ var TIMING = /* @__PURE__ */ new Set(["duration", "delay", "stagger", "with", "e
4035
4217
  var COLORS = /* @__PURE__ */ new Set(["fill", "stroke", "color"]);
4036
4218
  var NODES = /* @__PURE__ */ new Set(["into", "path"]);
4037
4219
  function branch(type) {
4038
- return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _112 => _112.properties, 'optionalAccess', _113 => _113.type, 'optionalAccess', _114 => _114.const]) === type);
4220
+ return (_nullishCoalesce(MOTION_COMMAND_SCHEMA.oneOf, () => ( []))).find((b) => _optionalChain([b, 'access', _138 => _138.properties, 'optionalAccess', _139 => _139.type, 'optionalAccess', _140 => _140.const]) === type);
4039
4221
  }
4040
4222
  function unitOf(type, name) {
4041
4223
  if (name === "duration" || name === "delay" || name === "stagger") return "ms";
@@ -4070,7 +4252,7 @@ function kindOf(name, schema) {
4070
4252
  }
4071
4253
  function motionCommandFields(type) {
4072
4254
  const schema = branch(type);
4073
- if (!_optionalChain([schema, 'optionalAccess', _115 => _115.properties])) return [];
4255
+ if (!_optionalChain([schema, 'optionalAccess', _141 => _141.properties])) return [];
4074
4256
  const required = new Set(_nullishCoalesce(schema.required, () => ( [])));
4075
4257
  const fields = Object.entries(schema.properties).filter(([name]) => name !== "type").map(([name, s]) => {
4076
4258
  const kind = kindOf(name, s);
@@ -4127,7 +4309,7 @@ function defaultMotionCommand(type, targets, scene) {
4127
4309
  if (type === "wait") return { type: "wait", duration: 500 };
4128
4310
  if (!targets.length) return { error: "Pick a target first: click an element on the stage." };
4129
4311
  const nodes = [...indexMotionNodes(scene.artwork.nodes).values()];
4130
- const other = (pred) => _optionalChain([nodes, 'access', _116 => _116.find, 'call', _117 => _117((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _118 => _118.node, 'access', _119 => _119.id]);
4312
+ const other = (pred) => _optionalChain([nodes, 'access', _142 => _142.find, 'call', _143 => _143((e) => !targets.includes(e.node.id) && pred(e)), 'optionalAccess', _144 => _144.node, 'access', _145 => _145.id]);
4131
4313
  const base = { targets: [...targets] };
4132
4314
  switch (type) {
4133
4315
  case "scale":
@@ -4158,8 +4340,8 @@ function defaultMotionCommand(type, targets, scene) {
4158
4340
  }
4159
4341
  case "effect": {
4160
4342
  for (const id of targets) {
4161
- const node = _optionalChain([nodes, 'access', _120 => _120.find, 'call', _121 => _121((e) => e.node.id === id), 'optionalAccess', _122 => _122.node]);
4162
- for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _123 => _123.effects]), () => ( []))) {
4343
+ const node = _optionalChain([nodes, 'access', _146 => _146.find, 'call', _147 => _147((e) => e.node.id === id), 'optionalAccess', _148 => _148.node]);
4344
+ for (const effect of _nullishCoalesce(_optionalChain([node, 'optionalAccess', _149 => _149.effects]), () => ( []))) {
4163
4345
  const param = Object.entries(effect.params).find(([, v]) => typeof v === "number");
4164
4346
  if (param) return { type, ...base, effect: effect.id, param: param[0], to: param[1] + 1 };
4165
4347
  }
@@ -4184,11 +4366,11 @@ function motionCommandRows(scene, shotId) {
4184
4366
  return scene.shots[index].run.map((command2, i) => {
4185
4367
  const t = timeline.commands.find((c) => c.shotIndex === index && c.command === i);
4186
4368
  const primitive = motionFlairPrimitive(command2);
4187
- return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _124 => _124.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _125 => _125.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
4369
+ return { index: i, command: command2, summary: motionCommandSummary(command2), start: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _150 => _150.start]), () => ( 0)), end: _nullishCoalesce(_optionalChain([t, 'optionalAccess', _151 => _151.end]), () => ( 0)), ...primitive ? { flairPrimitive: primitive } : {} };
4188
4370
  });
4189
4371
  }
4190
4372
  function duplicateMotionCommandEdit(scene, shot, command2) {
4191
- const run = _nullishCoalesce(_optionalChain([scene, 'access', _126 => _126.shots, 'access', _127 => _127.find, 'call', _128 => _128((s) => s.id === shot), 'optionalAccess', _129 => _129.run]), () => ( []));
4373
+ const run = _nullishCoalesce(_optionalChain([scene, 'access', _152 => _152.shots, 'access', _153 => _153.find, 'call', _154 => _154((s) => s.id === shot), 'optionalAccess', _155 => _155.run]), () => ( []));
4192
4374
  return { op: "add_commands", shot, commands: run[command2] ? [structuredClone(run[command2])] : [], at: command2 + 1 };
4193
4375
  }
4194
4376
  var MOTION_EASE_CHOICES = [
@@ -4273,7 +4455,7 @@ function motionCueFromKey(key) {
4273
4455
  function motionCueChoices(scene, limit = 256) {
4274
4456
  const choices = [];
4275
4457
  const s = (ms) => `${(ms / 1e3).toFixed(2)} s`;
4276
- const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _130 => _130.narration, 'optionalAccess', _131 => _131.words, 'access', _132 => _132.map, 'call', _133 => _133((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
4458
+ const end = Math.max(compileMotionScene(scene).duration, ..._nullishCoalesce(_optionalChain([scene, 'access', _156 => _156.narration, 'optionalAccess', _157 => _157.words, 'access', _158 => _158.map, 'call', _159 => _159((w) => _nullishCoalesce(w.end, () => ( w.start)))]), () => ( [0]))) + 2e3;
4277
4459
  if (scene.beat && scene.beat.bpm > 0) {
4278
4460
  for (let beat = 0; choices.length < limit; beat++) {
4279
4461
  const time = motionBeatTime(scene.beat, beat);
@@ -4283,7 +4465,7 @@ function motionCueChoices(scene, limit = 256) {
4283
4465
  choices.push({ value: `beat:${beat}`, label: `Beat ${beat} (bar ${at.bar}.${at.beatInBar}) \xB7 ${s(time)}`, time });
4284
4466
  }
4285
4467
  }
4286
- _optionalChain([scene, 'access', _134 => _134.narration, 'optionalAccess', _135 => _135.words, 'access', _136 => _136.forEach, 'call', _137 => _137((w, i) => {
4468
+ _optionalChain([scene, 'access', _160 => _160.narration, 'optionalAccess', _161 => _161.words, 'access', _162 => _162.forEach, 'call', _163 => _163((w, i) => {
4287
4469
  if (choices.length < limit * 2) choices.push({ value: `word:${i}`, label: `Word ${i} \u201C${w.text}\u201D \xB7 ${s(w.start)}`, time: w.start });
4288
4470
  })]);
4289
4471
  return choices;
@@ -4292,7 +4474,7 @@ function motionShotCueTime(scene, cue) {
4292
4474
  return cue ? motionCueTime(scene, cue) : void 0;
4293
4475
  }
4294
4476
  function formatMotionNarration(narration) {
4295
- return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _138 => _138.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
4477
+ return (_nullishCoalesce(_optionalChain([narration, 'optionalAccess', _164 => _164.words]), () => ( []))).map((w) => `${w.start} ${w.text}`).join("\n");
4296
4478
  }
4297
4479
  function parseMotionNarration(text) {
4298
4480
  const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
@@ -4307,6 +4489,36 @@ function parseMotionNarration(text) {
4307
4489
  }
4308
4490
  return { narration: { words } };
4309
4491
  }
4492
+ function formatMotionDrivers(drivers) {
4493
+ return (_nullishCoalesce(drivers, () => ( []))).map((d) => `${d.target}.${d.property} <- ${d.source.node}.${d.source.property}${d.factor !== void 0 && d.factor !== 1 ? ` * ${d.factor}` : ""}${d.offset ? ` ${d.offset < 0 ? "-" : "+"} ${Math.abs(d.offset)}` : ""}${d.clamp ? ` [${d.clamp[0]}, ${d.clamp[1]}]` : ""}`).join("\n");
4494
+ }
4495
+ function parseMotionDrivers(text) {
4496
+ const props = MOTION_DRIVEN_PROPERTIES;
4497
+ const drivers = [];
4498
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
4499
+ for (const [i, line] of lines.entries()) {
4500
+ const m = /^([\w.-]+)\.(\w+)\s*<-\s*([\w.-]+)\.(\w+)\s*(?:\*\s*(-?[\d.]+(?:e-?\d+)?))?\s*(?:([+-])\s*([\d.]+(?:e-?\d+)?))?\s*(?:\[\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\])?$/.exec(line);
4501
+ if (!m) return { error: `Line ${i + 1} ("${line}") should read "target.property <- source.property", optionally "* factor", "+ offset" and "[min, max]", e.g. "dot.x <- box.x * 0.5".` };
4502
+ const [, target, property, node, sourceProperty, factor, sign, offset, min, max] = m;
4503
+ for (const p of [property, sourceProperty]) if (!props.includes(p)) return { error: `Line ${i + 1}: "${p}" is not a property drivers use; use ${MOTION_DRIVEN_PROPERTIES.join(", ")}.` };
4504
+ const d = { target, property, source: { node, property: sourceProperty } };
4505
+ if (factor !== void 0) d.factor = Number(factor);
4506
+ if (offset !== void 0) d.offset = (sign === "-" ? -1 : 1) * Number(offset);
4507
+ if (min !== void 0) d.clamp = [Number(min), Number(max)];
4508
+ if ([d.factor, d.offset, ..._nullishCoalesce(d.clamp, () => ( []))].some((n) => n !== void 0 && !Number.isFinite(n))) return { error: `Line ${i + 1} has a number that cannot be read.` };
4509
+ drivers.push(d);
4510
+ }
4511
+ return { drivers };
4512
+ }
4513
+
4514
+
4515
+
4516
+
4517
+
4518
+
4519
+
4520
+
4521
+
4310
4522
 
4311
4523
 
4312
4524
 
@@ -4434,5 +4646,5 @@ function parseMotionNarration(text) {
4434
4646
 
4435
4647
 
4436
4648
 
4437
- exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_CLIP_TARGET = MOTION_CLIP_TARGET; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_EASE_CHOICES = MOTION_EASE_CHOICES; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MOTION_TRIGGERS = MOTION_TRIGGERS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.clipsShapeProblems = clipsShapeProblems; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.defaultMotionCommand = defaultMotionCommand; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.duplicateMotionCommandEdit = duplicateMotionCommandEdit; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.fitMotionSceneEdits = fitMotionSceneEdits; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.formatMotionNarration = formatMotionNarration; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.highlightMotionSvg = highlightMotionSvg; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionClipAttrs = motionClipAttrs; exports.motionClipLayout = motionClipLayout; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionContactSheetSvg = motionContactSheetSvg; exports.motionCueChoices = motionCueChoices; exports.motionCueFromKey = motionCueFromKey; exports.motionCueKey = motionCueKey; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionEaseKey = motionEaseKey; exports.motionEasingCurve = motionEasingCurve; exports.motionEasingGuides = motionEasingGuides; exports.motionEasingPath = motionEasingPath; exports.motionFieldEdit = motionFieldEdit; exports.motionFieldLabel = motionFieldLabel; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; exports.motionNodeAtElement = motionNodeAtElement; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPath = motionPath; exports.motionPathLength = motionPathLength; exports.motionPathsSvg = motionPathsSvg; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionRulerTicks = motionRulerTicks; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotCueTime = motionShotCueTime; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionFieldInput = parseMotionFieldInput; exports.parseMotionNarration = parseMotionNarration; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.resolveMotionEditRefs = resolveMotionEditRefs; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.smoothMotionLegs = smoothMotionLegs; exports.smoothMotionTimes = smoothMotionTimes; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
4649
+ exports.ANIMATION_EDITOR_AGENT_INSTRUCTIONS = ANIMATION_EDITOR_AGENT_INSTRUCTIONS; exports.AnimationEditorController = AnimationEditorController; exports.FLAIR_EASES = FLAIR_EASES; exports.FLAIR_EXPORT_VERSION = FLAIR_EXPORT_VERSION; exports.MOTION_ADDITIVE_PROPERTIES = MOTION_ADDITIVE_PROPERTIES; exports.MOTION_BLENDS = MOTION_BLENDS; exports.MOTION_CAMERA_MS = MOTION_CAMERA_MS; exports.MOTION_CLIP_TARGET = MOTION_CLIP_TARGET; exports.MOTION_COMMAND_SCHEMA = MOTION_COMMAND_SCHEMA; exports.MOTION_COMMAND_TYPES = MOTION_COMMAND_TYPES; exports.MOTION_DEFAULT_DURATIONS = MOTION_DEFAULT_DURATIONS; exports.MOTION_DEFAULT_HOLD = MOTION_DEFAULT_HOLD; exports.MOTION_DIRECTIONS = MOTION_DIRECTIONS; exports.MOTION_DRAW_EFFECT_ID = MOTION_DRAW_EFFECT_ID; exports.MOTION_DRAW_MS_PER_UNIT = MOTION_DRAW_MS_PER_UNIT; exports.MOTION_DRAW_RANGE = MOTION_DRAW_RANGE; exports.MOTION_DRIVEN_PROPERTIES = MOTION_DRIVEN_PROPERTIES; exports.MOTION_EASE_CHOICES = MOTION_EASE_CHOICES; exports.MOTION_EASE_IN_OUT = MOTION_EASE_IN_OUT; exports.MOTION_EASE_OUT = MOTION_EASE_OUT; exports.MOTION_EDIT_OPS = MOTION_EDIT_OPS; exports.MOTION_ENTER_STYLES = MOTION_ENTER_STYLES; exports.MOTION_EXIT_STYLES = MOTION_EXIT_STYLES; exports.MOTION_IRIS_SHAPES = MOTION_IRIS_SHAPES; exports.MOTION_ISSUE_CODES = MOTION_ISSUE_CODES; exports.MOTION_MIN_CHANGE_MS = MOTION_MIN_CHANGE_MS; exports.MOTION_REPEAT_MODES = MOTION_REPEAT_MODES; exports.MOTION_SPEED_RANGE = MOTION_SPEED_RANGE; exports.MOTION_SPLIT_ORIENTATIONS = MOTION_SPLIT_ORIENTATIONS; exports.MOTION_TRIGGERS = MOTION_TRIGGERS; exports.MotionSceneFormatError = MotionSceneFormatError; exports.activeSegment = activeSegment; exports.applyMotionControlValues = applyMotionControlValues; exports.applyMotionDrivers = applyMotionDrivers; exports.applyMotionEdits = applyMotionEdits; exports.checkMotionScene = checkMotionScene; exports.clipsShapeProblems = clipsShapeProblems; exports.commandShapeProblems = commandShapeProblems; exports.compileMotionScene = compileMotionScene; exports.createAnimationEditorToolProvider = createAnimationEditorToolProvider; exports.createMotionScene = createMotionScene; exports.defaultMotionCommand = defaultMotionCommand; exports.describeMotionElements = describeMotionElements; exports.describeMotionShots = describeMotionShots; exports.driversShapeProblems = driversShapeProblems; exports.duplicateMotionCommandEdit = duplicateMotionCommandEdit; exports.fitMotionRect = fitMotionRect; exports.fitMotionScene = fitMotionScene; exports.fitMotionSceneEdits = fitMotionSceneEdits; exports.flairAtLeast = flairAtLeast; exports.flairCurveFor = flairCurveFor; exports.flairEaseFor = flairEaseFor; exports.formatMotionDrivers = formatMotionDrivers; exports.formatMotionNarration = formatMotionNarration; exports.freshShotId = freshShotId; exports.fromFlairSvgMotion = fromFlairSvgMotion; exports.fromFlairVectorEffect = fromFlairVectorEffect; exports.highlightMotionSvg = highlightMotionSvg; exports.indexMotionNodes = indexMotionNodes; exports.interpolateMotionValue = interpolateMotionValue; exports.isMotionCommand = isMotionCommand; exports.isMotionShot = isMotionShot; exports.matchMotionNodes = matchMotionNodes; exports.motionAddTrackKey = motionAddTrackKey; exports.motionBeatAt = motionBeatAt; exports.motionBeatTime = motionBeatTime; exports.motionBlurTimes = motionBlurTimes; exports.motionClipAttrs = motionClipAttrs; exports.motionClipLayout = motionClipLayout; exports.motionCommandDuration = motionCommandDuration; exports.motionCommandFields = motionCommandFields; exports.motionCommandRepeat = motionCommandRepeat; exports.motionCommandRows = motionCommandRows; exports.motionCommandSummary = motionCommandSummary; exports.motionContactSheetSvg = motionContactSheetSvg; exports.motionCueChoices = motionCueChoices; exports.motionCueFromKey = motionCueFromKey; exports.motionCueKey = motionCueKey; exports.motionCueTime = motionCueTime; exports.motionCycleDuration = motionCycleDuration; exports.motionDrivenNodes = motionDrivenNodes; exports.motionDriverSources = motionDriverSources; exports.motionEaseKey = motionEaseKey; exports.motionEasingCurve = motionEasingCurve; exports.motionEasingGuides = motionEasingGuides; exports.motionEasingPath = motionEasingPath; exports.motionFieldEdit = motionFieldEdit; exports.motionFieldLabel = motionFieldLabel; exports.motionFlairPrimitive = motionFlairPrimitive; exports.motionFollowRoute = motionFollowRoute; exports.motionFrameAt = motionFrameAt; exports.motionFrameToSvg = motionFrameToSvg; exports.motionGhosts = motionGhosts; exports.motionIssue = motionIssue; exports.motionIssueKey = motionIssueKey; exports.motionIssueLine = motionIssueLine; exports.motionMovingIds = motionMovingIds; exports.motionNodeAtElement = motionNodeAtElement; exports.motionNodeClips = motionNodeClips; exports.motionNodeOffset = motionNodeOffset; exports.motionPath = motionPath; exports.motionPathLength = motionPathLength; exports.motionPathsSvg = motionPathsSvg; exports.motionPropertyValue = motionPropertyValue; exports.motionRestValue = motionRestValue; exports.motionRevealClip = motionRevealClip; exports.motionRevealPadding = motionRevealPadding; exports.motionRulerTicks = motionRulerTicks; exports.motionSceneShapeProblems = motionSceneShapeProblems; exports.motionSceneToJson = motionSceneToJson; exports.motionShotAt = motionShotAt; exports.motionShotCueTime = motionShotCueTime; exports.motionShotTimeScale = motionShotTimeScale; exports.motionStageSvg = motionStageSvg; exports.motionTrackKey = motionTrackKey; exports.namedWithSuggestion = namedWithSuggestion; exports.parseMotionDrivers = parseMotionDrivers; exports.parseMotionFieldInput = parseMotionFieldInput; exports.parseMotionNarration = parseMotionNarration; exports.parseMotionScene = parseMotionScene; exports.resolveMotionCamera = resolveMotionCamera; exports.resolveMotionEditRefs = resolveMotionEditRefs; exports.segmentProgress = segmentProgress; exports.segmentValue = segmentValue; exports.shotShapeProblems = shotShapeProblems; exports.smoothMotionLegs = smoothMotionLegs; exports.smoothMotionTimes = smoothMotionTimes; exports.suggestName = suggestName; exports.toFlairEffectDocument = toFlairEffectDocument; exports.toFlairSvgMotion = toFlairSvgMotion; exports.toFlairVectorEffect = toFlairVectorEffect; exports.toLottie = toLottie; exports.trackValue = trackValue; exports.withMotionLayers = withMotionLayers; exports.withViewBox = withViewBox;
4438
4650
  //# sourceMappingURL=index.cjs.map