@pixerate/editor 0.64.0 → 0.65.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.
@@ -910,6 +910,77 @@ declare function motionFrameToSvg(scene: MotionScene, time: number, options?: Mo
910
910
  /** Replaces the root `viewBox` of an SVG string. */
911
911
  declare function withViewBox(svg: string, box: MotionRect): string;
912
912
 
913
+ /**
914
+ * Seeing motion on a still: onion skin (faded frames either side of the playhead) and motion
915
+ * paths (where each node's centre travels). Frames are pure functions of time, so both are just
916
+ * more frames: no state, and the same picture every time.
917
+ */
918
+ interface MotionOnionSkin {
919
+ /** Ghost frames before the playhead (default 2). */
920
+ before?: number;
921
+ /** Ghost frames after the playhead (default 0). */
922
+ after?: number;
923
+ /**
924
+ * Time between ghosts: ms (default 1000 / 12, every other frame at 24 fps), or `commands` to
925
+ * ghost the moments commands start and end around the playhead.
926
+ */
927
+ spacing?: number | 'commands';
928
+ /** Opacity of the nearest ghost (default 0.35); further ones fade as 1 / distance. */
929
+ opacity?: number;
930
+ }
931
+ interface MotionGhost {
932
+ time: number;
933
+ /** Negative before the playhead, positive after: 1 is the nearest. */
934
+ offset: number;
935
+ opacity: number;
936
+ }
937
+ /** The ghost frames for a playhead at `time`, nearest first on each side. */
938
+ declare function motionGhosts(scene: MotionScene, time: number, onion?: MotionOnionSkin): MotionGhost[];
939
+ interface MotionPathOptions {
940
+ /** From this time, ms (default 0). */
941
+ from?: number;
942
+ /** To this time, ms (default the end). */
943
+ to?: number;
944
+ /** Time between samples, ms (default 1000 / 30; at most 400 samples). */
945
+ step?: number;
946
+ }
947
+ interface MotionPath {
948
+ id: string;
949
+ /** Where the node's centre is, in document units, sample by sample. */
950
+ points: {
951
+ t: number;
952
+ x: number;
953
+ y: number;
954
+ }[];
955
+ /** Where it is when a change that moves it (or a group it is in) starts or ends. */
956
+ keys: {
957
+ t: number;
958
+ x: number;
959
+ y: number;
960
+ }[];
961
+ }
962
+ /** Ids of the nodes whose place changes (they or a group they are in move, turn, scale, follow or morph). */
963
+ declare function motionMovingIds(scene: MotionScene): string[];
964
+ /** Where a node's centre travels over time (its bounds as drawn, in document units). */
965
+ declare function motionPath(scene: MotionScene, id: string, options?: MotionPathOptions): MotionPath;
966
+ /** Motion paths as an SVG overlay: a line per node and a dot where each change starts or ends. Stroke widths follow the camera so they read the same at any zoom. */
967
+ declare function motionPathsSvg(paths: readonly MotionPath[], camera: MotionRect, color?: string): string;
968
+ /** What the stage can draw over a frame. */
969
+ interface MotionOverlayOptions {
970
+ /** Faded frames either side of the playhead. */
971
+ onionSkin?: MotionOnionSkin;
972
+ /** Draw where nodes travel: true for every node whose place changes, or a list of ids. */
973
+ motionPaths?: boolean | readonly string[];
974
+ /** Colour of motion paths (default `#f43f5e`). */
975
+ motionPathColor?: string;
976
+ }
977
+ /**
978
+ * The frame at `time` as SVG with overlays: ghost frames under it (each with its own id prefix,
979
+ * element ids dropped so they never clash) and motion paths over it. Without overlays it is
980
+ * `motionFrameToSvg`.
981
+ */
982
+ declare function motionStageSvg(scene: MotionScene, time: number, options?: MotionSvgOptions & MotionOverlayOptions): string;
983
+
913
984
  /** Changes shorter than this (ms) are too quick to follow. */
914
985
  declare const MOTION_MIN_CHANGE_MS = 120;
915
986
  /**
@@ -1408,7 +1479,7 @@ declare class AnimationEditorController {
1408
1479
  /** The scene at `time` (default: the playhead). */
1409
1480
  frame(time?: number, options?: MotionFrameOptions): MotionFrame;
1410
1481
  /** The scene at `time` (default: the playhead) as SVG. */
1411
- toSvg(time?: number, options?: MotionSvgOptions): string;
1482
+ toSvg(time?: number, options?: MotionSvgOptions & MotionOverlayOptions): string;
1412
1483
  /** The scene as a Flair `SvgMotion`. */
1413
1484
  toFlairMotion(options?: FlairExportOptions): FlairMotionExport;
1414
1485
  /** The scene as a Flair catalog vector effect. */
@@ -1848,6 +1919,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1848
1919
  * A short authoring guide for a model writing vector animations with the animation editor's
1849
1920
  * tools. Static, so it caches well as part of a system prompt.
1850
1921
  */
1851
- declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- 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.\n- 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.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- 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.\n- 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.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- 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.\n- \"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.\n- 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.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
1922
+ declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- 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.\n- 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.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- 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.\n- 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.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- 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.\n- \"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.\n- 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.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
1852
1923
 
1853
- export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, type LottieAnimation, type LottieExport, type LottieExportOptions, type LottieKeyframe, type LottieShape, type LottieValue, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
1924
+ export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, type LottieAnimation, type LottieExport, type LottieExportOptions, type LottieKeyframe, type LottieShape, type LottieValue, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionGhost, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionOnionSkin, type MotionOverlayOptions, type MotionPath, type MotionPathOptions, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
@@ -910,6 +910,77 @@ declare function motionFrameToSvg(scene: MotionScene, time: number, options?: Mo
910
910
  /** Replaces the root `viewBox` of an SVG string. */
911
911
  declare function withViewBox(svg: string, box: MotionRect): string;
912
912
 
913
+ /**
914
+ * Seeing motion on a still: onion skin (faded frames either side of the playhead) and motion
915
+ * paths (where each node's centre travels). Frames are pure functions of time, so both are just
916
+ * more frames: no state, and the same picture every time.
917
+ */
918
+ interface MotionOnionSkin {
919
+ /** Ghost frames before the playhead (default 2). */
920
+ before?: number;
921
+ /** Ghost frames after the playhead (default 0). */
922
+ after?: number;
923
+ /**
924
+ * Time between ghosts: ms (default 1000 / 12, every other frame at 24 fps), or `commands` to
925
+ * ghost the moments commands start and end around the playhead.
926
+ */
927
+ spacing?: number | 'commands';
928
+ /** Opacity of the nearest ghost (default 0.35); further ones fade as 1 / distance. */
929
+ opacity?: number;
930
+ }
931
+ interface MotionGhost {
932
+ time: number;
933
+ /** Negative before the playhead, positive after: 1 is the nearest. */
934
+ offset: number;
935
+ opacity: number;
936
+ }
937
+ /** The ghost frames for a playhead at `time`, nearest first on each side. */
938
+ declare function motionGhosts(scene: MotionScene, time: number, onion?: MotionOnionSkin): MotionGhost[];
939
+ interface MotionPathOptions {
940
+ /** From this time, ms (default 0). */
941
+ from?: number;
942
+ /** To this time, ms (default the end). */
943
+ to?: number;
944
+ /** Time between samples, ms (default 1000 / 30; at most 400 samples). */
945
+ step?: number;
946
+ }
947
+ interface MotionPath {
948
+ id: string;
949
+ /** Where the node's centre is, in document units, sample by sample. */
950
+ points: {
951
+ t: number;
952
+ x: number;
953
+ y: number;
954
+ }[];
955
+ /** Where it is when a change that moves it (or a group it is in) starts or ends. */
956
+ keys: {
957
+ t: number;
958
+ x: number;
959
+ y: number;
960
+ }[];
961
+ }
962
+ /** Ids of the nodes whose place changes (they or a group they are in move, turn, scale, follow or morph). */
963
+ declare function motionMovingIds(scene: MotionScene): string[];
964
+ /** Where a node's centre travels over time (its bounds as drawn, in document units). */
965
+ declare function motionPath(scene: MotionScene, id: string, options?: MotionPathOptions): MotionPath;
966
+ /** Motion paths as an SVG overlay: a line per node and a dot where each change starts or ends. Stroke widths follow the camera so they read the same at any zoom. */
967
+ declare function motionPathsSvg(paths: readonly MotionPath[], camera: MotionRect, color?: string): string;
968
+ /** What the stage can draw over a frame. */
969
+ interface MotionOverlayOptions {
970
+ /** Faded frames either side of the playhead. */
971
+ onionSkin?: MotionOnionSkin;
972
+ /** Draw where nodes travel: true for every node whose place changes, or a list of ids. */
973
+ motionPaths?: boolean | readonly string[];
974
+ /** Colour of motion paths (default `#f43f5e`). */
975
+ motionPathColor?: string;
976
+ }
977
+ /**
978
+ * The frame at `time` as SVG with overlays: ghost frames under it (each with its own id prefix,
979
+ * element ids dropped so they never clash) and motion paths over it. Without overlays it is
980
+ * `motionFrameToSvg`.
981
+ */
982
+ declare function motionStageSvg(scene: MotionScene, time: number, options?: MotionSvgOptions & MotionOverlayOptions): string;
983
+
913
984
  /** Changes shorter than this (ms) are too quick to follow. */
914
985
  declare const MOTION_MIN_CHANGE_MS = 120;
915
986
  /**
@@ -1408,7 +1479,7 @@ declare class AnimationEditorController {
1408
1479
  /** The scene at `time` (default: the playhead). */
1409
1480
  frame(time?: number, options?: MotionFrameOptions): MotionFrame;
1410
1481
  /** The scene at `time` (default: the playhead) as SVG. */
1411
- toSvg(time?: number, options?: MotionSvgOptions): string;
1482
+ toSvg(time?: number, options?: MotionSvgOptions & MotionOverlayOptions): string;
1412
1483
  /** The scene as a Flair `SvgMotion`. */
1413
1484
  toFlairMotion(options?: FlairExportOptions): FlairMotionExport;
1414
1485
  /** The scene as a Flair catalog vector effect. */
@@ -1848,6 +1919,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1848
1919
  * A short authoring guide for a model writing vector animations with the animation editor's
1849
1920
  * tools. Static, so it caches well as part of a system prompt.
1850
1921
  */
1851
- declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- 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.\n- 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.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- 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.\n- 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.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- 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.\n- \"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.\n- 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.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
1922
+ declare const ANIMATION_EDITOR_AGENT_INSTRUCTIONS = "You animate vector artwork. A scene is artwork (vector paths and groups, each with an id) plus shots.\n\nHow it plays\n- Shots play in order. A shot optionally moves the camera, plays its commands, then holds (default 600 ms).\n- Commands in a shot play one after another; give \"with\": \"previous\" to start one together with the command before, \"delay\" to wait, \"stagger\" to offset several targets.\n- Timing comes from the content: each command has a default length (enter 600 ms, move 700 ms, draw follows the path length). Only set \"duration\" to change the pace.\n- Anything that enters later is hidden until it enters. Things that never enter are visible from the start.\n- Every frame is a pure function of time: the same scene always plays the same frames.\n\nCommands\n- enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.\n- 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.\n- 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.\n- enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting \"scale\".\n- morph turns a shape into another node's shape (keep the target hidden); match does the same for a whole group, part by part (Smart Animate: draw the end state as a second, hidden group whose parts share names with the first); follow moves along a path node (with \"rotate\", moves made meanwhile turn with the path).\n- Emphasis draws the eye and returns to rest: pulse (swell), shake (side to side, or \"axis\": \"vertical\"), teeter (rock), wave (bob, no settling), wiggle (seeded wander; the same seed wanders the same way) and color-pulse (tint and back).\n- 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.\n- 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.\n- Springs (\"spring-gentle\", \"spring-quick\", \"spring-bouncy\", \"spring-slow\" or { \"type\": \"spring\", \u2026 }) feel physical; leave \"duration\" out and the spring sets it.\n- 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.\n- \"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.\n- 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.\n- effect tweens a numeric parameter of a live path effect already on the node (twist angle, wiggle time, zig zag size).\n\nWorking method\n1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).\n2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).\n3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + \"end\") to look at the result; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.\n4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.\n\nKeep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.";
1852
1923
 
1853
- export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, type LottieAnimation, type LottieExport, type LottieExportOptions, type LottieKeyframe, type LottieShape, type LottieValue, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
1924
+ export { ANIMATION_EDITOR_AGENT_INSTRUCTIONS, AnimationEditorController, type AnimationEditorOptions, type AnimationEditorSelection, type AnimationEditorState, type AnimationEditorToolProviderOptions, FLAIR_EASES, FLAIR_EXPORT_VERSION, type FlairEase, type FlairEaseName, type FlairEffectDocument, type FlairEffectOptions, type FlairExportOptions, type FlairImportOptions, type FlairImportResult, type FlairMotionExport, type FlairSpringEase, type FlairSvgAnimation, type FlairSvgMotion, type FlairVectorEffect, type LottieAnimation, type LottieExport, type LottieExportOptions, type LottieKeyframe, type LottieShape, type LottieValue, MOTION_ADDITIVE_PROPERTIES, MOTION_BLENDS, MOTION_CAMERA_MS, MOTION_COMMAND_SCHEMA, MOTION_COMMAND_TYPES, MOTION_DEFAULT_DURATIONS, MOTION_DEFAULT_HOLD, MOTION_DIRECTIONS, MOTION_DRAW_EFFECT_ID, MOTION_DRAW_MS_PER_UNIT, MOTION_DRAW_RANGE, MOTION_EASE_IN_OUT, MOTION_EASE_OUT, MOTION_EDIT_OPS, MOTION_ENTER_STYLES, MOTION_EXIT_STYLES, MOTION_IRIS_SHAPES, MOTION_ISSUE_CODES, MOTION_MIN_CHANGE_MS, MOTION_REPEAT_MODES, MOTION_SPEED_RANGE, MOTION_SPLIT_ORIENTATIONS, type MotionBeatGrid, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionCue, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFit, type MotionFitOptions, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionGhost, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, type MotionNarration, type MotionNodeEntry, type MotionOnionSkin, type MotionOverlayOptions, type MotionPath, type MotionPathOptions, type MotionPreset, type MotionProperty, type MotionPulseCommand, type MotionRect, type MotionReveal, type MotionRotateCommand, type MotionScaleCommand, type MotionScene, MotionSceneFormatError, type MotionSegment, type MotionShakeCommand, type MotionShot, type MotionShotTiming, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, describeMotionElements, describeMotionShots, fitMotionRect, fitMotionScene, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCueTime, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
@@ -1258,6 +1258,88 @@ function withViewBox(svg, box) {
1258
1258
  return svg.replace(/viewBox="[^"]*"/, `viewBox="${f(box.x)} ${f(box.y)} ${f(box.width)} ${f(box.height)}"`);
1259
1259
  }
1260
1260
 
1261
+ // src/animation-editor/onion.ts
1262
+ function motionGhosts(scene, time, onion = {}) {
1263
+ const before = Math.max(0, Math.floor(onion.before ?? 2));
1264
+ const after = Math.max(0, Math.floor(onion.after ?? 0));
1265
+ const base = Math.min(1, Math.max(0, onion.opacity ?? 0.35));
1266
+ const timeline = compileMotionScene(scene);
1267
+ const ghosts = [];
1268
+ const push = (t, offset) => ghosts.push({ time: t, offset, opacity: Math.round(base / Math.abs(offset) * 1e3) / 1e3 });
1269
+ if (onion.spacing === "commands") {
1270
+ const marks = [...new Set(timeline.commands.flatMap((c) => [c.start, c.end]))].sort((a, b) => a - b);
1271
+ marks.filter((t) => t < time - 1e-6).reverse().slice(0, before).forEach((t, i) => push(t, -(i + 1)));
1272
+ marks.filter((t) => t > time + 1e-6).slice(0, after).forEach((t, i) => push(t, i + 1));
1273
+ return ghosts;
1274
+ }
1275
+ const spacing = typeof onion.spacing === "number" && onion.spacing > 0 ? onion.spacing : 1e3 / 12;
1276
+ for (let i = 1; i <= before && time - i * spacing >= 0; i++) push(time - i * spacing, -i);
1277
+ for (let i = 1; i <= after && time + i * spacing <= timeline.duration; i++) push(time + i * spacing, i);
1278
+ return ghosts;
1279
+ }
1280
+ var MOVING = ["x", "y", "rotation", "scaleX", "scaleY", "follow", "shape"];
1281
+ function motionMovingIds(scene) {
1282
+ const timeline = compileMotionScene(scene);
1283
+ const nodes = indexMotionNodes(scene.artwork.nodes);
1284
+ const moving = new Set(timeline.segments.filter((s) => s.node && MOVING.includes(s.property)).map((s) => s.node));
1285
+ return [...nodes.values()].filter((e) => e.node.visible !== false && (moving.has(e.node.id) || e.ancestors.some((a) => moving.has(a)))).map((e) => e.node.id);
1286
+ }
1287
+ function motionPath(scene, id, options = {}) {
1288
+ const nodes = indexMotionNodes(scene.artwork.nodes);
1289
+ const entry = nodes.get(id);
1290
+ if (!entry) throw new Error(`There is no element "${id}" in the artwork.`);
1291
+ const timeline = compileMotionScene(scene);
1292
+ const from = Math.max(0, options.from ?? 0);
1293
+ const to = Math.min(timeline.duration, options.to ?? timeline.duration);
1294
+ let step = options.step && options.step > 0 ? options.step : 1e3 / 30;
1295
+ if ((to - from) / step > 400) step = (to - from) / 400;
1296
+ const r = (n) => Math.round(n * 100) / 100;
1297
+ const centreAt = (t) => {
1298
+ const node = indexMotionNodes(motionFrameAtCache(scene, t)).get(id);
1299
+ const b = node?.bounds;
1300
+ return b ? { t: r(t), x: r(b.x + b.width / 2), y: r(b.y + b.height / 2) } : null;
1301
+ };
1302
+ const points = [];
1303
+ for (let t = from; t <= to + 1e-6; t += step) {
1304
+ const p = centreAt(Math.min(t, to));
1305
+ if (p) points.push(p);
1306
+ }
1307
+ const owners = /* @__PURE__ */ new Set([id, ...entry.ancestors]);
1308
+ const times = [...new Set(timeline.segments.filter((s) => owners.has(s.node) && MOVING.includes(s.property)).flatMap((s) => [s.start, s.end]))].filter((t) => t >= from && t <= to).sort((a, b) => a - b);
1309
+ const keys = times.map(centreAt).filter((p) => p !== null);
1310
+ return { id, points, keys };
1311
+ }
1312
+ function motionFrameAtCache(scene, t) {
1313
+ return motionFrameAt(scene, t).document.nodes;
1314
+ }
1315
+ function motionPathsSvg(paths, camera, color = "#f43f5e") {
1316
+ const w = Math.max(camera.width, camera.height) / 400;
1317
+ const f = (n) => String(Math.round(n * 100) / 100);
1318
+ return paths.filter((p) => p.points.length > 1).map(
1319
+ (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>`
1320
+ ).join("");
1321
+ }
1322
+ var innerSvg = (svg) => svg.slice(svg.indexOf(">") + 1, svg.lastIndexOf("</svg>"));
1323
+ function motionStageSvg(scene, time, options = {}) {
1324
+ const { onionSkin, motionPaths, motionPathColor, ...svgOptions } = options;
1325
+ const current = motionFrameToSvg(scene, time, svgOptions);
1326
+ if (!onionSkin && !motionPaths) return current;
1327
+ const prefix = svgOptions.idPrefix ?? "vg-";
1328
+ const ghosts = onionSkin ? motionGhosts(scene, time, onionSkin).map((g, i) => {
1329
+ const own = `${prefix}ghost${i}-`;
1330
+ const inner = innerSvg(motionFrameToSvg(scene, g.time, { ...svgOptions, idPrefix: own, background: false })).replace(new RegExp(` id="(?!${own.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})[^"]*"`, "g"), "");
1331
+ return `<g data-onion="${g.offset < 0 ? "before" : "after"}" opacity="${g.opacity}">${inner}</g>`;
1332
+ }) : [];
1333
+ const camera = motionFrameAt(scene, time).camera;
1334
+ const ids = motionPaths === true ? motionMovingIds(scene) : motionPaths ? [...motionPaths] : [];
1335
+ const paths = ids.length ? motionPathsSvg(ids.map((id) => motionPath(scene, id)), camera, motionPathColor) : "";
1336
+ const open = current.indexOf(">") + 1;
1337
+ const close = current.lastIndexOf("</svg>");
1338
+ const body = current.slice(open, close);
1339
+ const bg = svgOptions.background !== false && scene.artwork.background ? /^\s*<rect[^>]*\/>/.exec(body)?.[0] ?? "" : "";
1340
+ return `${current.slice(0, open)}${bg}${ghosts.join("")}${body.slice(bg.length)}${paths}</svg>`;
1341
+ }
1342
+
1261
1343
  // src/animation-editor/check.ts
1262
1344
  var MOTION_MIN_CHANGE_MS = 120;
1263
1345
  var NUMERIC_PARAMS = (node, effectId) => {
@@ -2701,7 +2783,7 @@ var AnimationEditorController = class {
2701
2783
  }
2702
2784
  /** The scene at `time` (default: the playhead) as SVG. */
2703
2785
  toSvg(time = this.player.currentTime, options = {}) {
2704
- return motionFrameToSvg(this.scene, time, options);
2786
+ return motionStageSvg(this.scene, time, options);
2705
2787
  }
2706
2788
  /** The scene as a Flair `SvgMotion`. */
2707
2789
  toFlairMotion(options = {}) {
@@ -3061,10 +3143,20 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3061
3143
  time: MS("Time from the start"),
3062
3144
  shot: { ...SHOT_ID, description: 'Render a moment of this shot instead of "time".' },
3063
3145
  at: { type: "string", enum: ["start", "middle", "end"], description: "With shot: which moment (default end: everything drawn, before the next shot)." },
3064
- format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." }
3146
+ format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." },
3147
+ onion_skin: object(
3148
+ {
3149
+ before: { type: "integer", minimum: 0, maximum: 12, description: "Ghost frames before the moment (default 2)." },
3150
+ after: { type: "integer", minimum: 0, maximum: 12, description: "Ghost frames after it (default 0)." },
3151
+ spacing: { anyOf: [{ type: "number", exclusiveMinimum: 0 }, { type: "string", enum: ["commands"] }], description: 'Ms between ghosts (default 83, every other frame at 24 fps), or "commands" for the moments commands start and end.' }
3152
+ },
3153
+ [],
3154
+ "Draw faded frames either side of the moment, to see motion in one picture."
3155
+ ),
3156
+ motion_paths: { anyOf: [{ type: "boolean" }, { type: "array", items: { type: "string", minLength: 1 } }], description: "Draw where nodes travel (dashed, with a dot where each change starts or ends): true for every node whose place changes, or a list of ids." }
3065
3157
  }),
3066
3158
  annotations: { title: "Render a frame", ...READ },
3067
- handler: async ({ time, shot, at = "end", format = "svg" }) => {
3159
+ handler: async ({ time, shot, at = "end", format = "svg", onion_skin, motion_paths }) => {
3068
3160
  const s = scene();
3069
3161
  let t = time ?? controller.getState().time;
3070
3162
  if (shot !== void 0) {
@@ -3072,7 +3164,11 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3072
3164
  if (!timing) throw new Error(`There is no shot "${shot}"; the shots are ${s.shots.map((x) => `"${x.id}"`).join(", ") || "none yet"}.`);
3073
3165
  t = at === "start" ? timing.start : at === "middle" ? (timing.commandsStart + timing.commandsEnd) / 2 : Math.max(timing.start, timing.end - 1);
3074
3166
  }
3075
- const svg = controller.toSvg(t, { pretty: false });
3167
+ if (Array.isArray(motion_paths)) {
3168
+ const known = new Set(describeMotionElements(s).map((e) => e.id));
3169
+ for (const id of motion_paths) if (!known.has(id)) throw new Error(`There is no element ${namedWithSuggestion(id, known)} to draw a motion path for.`);
3170
+ }
3171
+ const svg = controller.toSvg(t, { pretty: false, ...onion_skin ? { onionSkin: onion_skin } : {}, ...motion_paths ? { motionPaths: motion_paths } : {} });
3076
3172
  if (format === "png") {
3077
3173
  if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_animation_frame with format "svg".');
3078
3174
  const image = await options.rasterize(svg, { width: s.artwork.width, height: s.artwork.height });
@@ -3120,6 +3216,24 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3120
3216
  };
3121
3217
  }
3122
3218
  };
3219
+ const getMotionPath = {
3220
+ name: "get_motion_path",
3221
+ title: "Read where a node travels",
3222
+ description: "Returns where a node's centre is over time, in document units (sampled), and where it is each time a change that moves it starts or ends. Use it to check a path, spacing or arc without reading pictures; render_animation_frame with motion_paths draws it.",
3223
+ inputSchema: object({
3224
+ node: { type: "string", minLength: 1, description: "Element id from get_animation." },
3225
+ from: { type: "number", minimum: 0, description: "From this time, ms (default 0)." },
3226
+ to: { type: "number", minimum: 0, description: "To this time, ms (default the end)." },
3227
+ step: { type: "number", exclusiveMinimum: 0, description: "Ms between samples (default 33; at most 400 samples)." }
3228
+ }, ["node"]),
3229
+ annotations: { title: "Read where a node travels", ...READ },
3230
+ handler: ({ node, from, to, step }) => {
3231
+ const s = scene();
3232
+ const ids = describeMotionElements(s).map((e) => e.id);
3233
+ if (!ids.includes(node)) throw new Error(`There is no element ${namedWithSuggestion(node, ids)}.`);
3234
+ return motionPath(s, node, { from, to, step });
3235
+ }
3236
+ };
3123
3237
  const exportFlairMotion = {
3124
3238
  name: "export_flair_motion",
3125
3239
  title: "Export as Flair vector motion",
@@ -3383,7 +3497,7 @@ function createAnimationEditorToolProvider(controller, options = {}) {
3383
3497
  return { redone, ...redone ? { label } : {} };
3384
3498
  }
3385
3499
  };
3386
- const reads = [getAnimation, getShot, findElements, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
3500
+ const reads = [getAnimation, getShot, findElements, getMotionPath, getTimingGrid, checkAnimation, renderFrame, exportFlairMotion, exportFlairEffect, exportLottie, seekAnimation];
3387
3501
  const writes = [setArtworkSvg, fitAnimation, importFlairEffect, insertShots, updateShot, deleteShots, moveShot, addCommands, updateCommand, deleteCommands, setSettings, setControls, editAnimation, writeAnimation, undo, redo];
3388
3502
  return {
3389
3503
  id: options.id ?? "animation",
@@ -3430,7 +3544,7 @@ Commands
3430
3544
  Working method
3431
3545
  1. get_animation to learn the element ids (set_animation_artwork_svg first if there is no artwork: give every element you will animate an id; or import_flair_effect to start from a Flair effect and edit it).
3432
3546
  2. Plan a few shots, each one idea. Add them with insert_shots, or edit_animation to make several edits as one step (dry_run first when unsure).
3433
- 3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result.
3547
+ 3. To hit a length, fit_animation (keep the shots whose pace matters). check_animation and fix every error and warning; render_animation_frame (shot + "end") to look at the result; add onion_skin or motion_paths to see the motion in one picture, or get_motion_path for the numbers.
3434
3548
  4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
3435
3549
 
3436
3550
  Keep motion purposeful: one thing moving at a time reads best, ease-out for arrivals, ease-in for exits, and hold long enough to see each result.`;
@@ -3497,12 +3611,16 @@ export {
3497
3611
  motionFollowRoute,
3498
3612
  motionFrameAt,
3499
3613
  motionFrameToSvg,
3614
+ motionGhosts,
3500
3615
  motionIssue,
3501
3616
  motionIssueKey,
3502
3617
  motionIssueLine,
3618
+ motionMovingIds,
3503
3619
  motionNodeClips,
3504
3620
  motionNodeOffset,
3621
+ motionPath,
3505
3622
  motionPathLength,
3623
+ motionPathsSvg,
3506
3624
  motionRestValue,
3507
3625
  motionRevealClip,
3508
3626
  motionRevealPadding,
@@ -3510,6 +3628,7 @@ export {
3510
3628
  motionSceneToJson,
3511
3629
  motionShotAt,
3512
3630
  motionShotTimeScale,
3631
+ motionStageSvg,
3513
3632
  motionTrackKey,
3514
3633
  namedWithSuggestion,
3515
3634
  parseMotionScene,