@pixerate/editor 0.67.0 → 0.68.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.
@@ -31,8 +31,8 @@ interface MotionScene {
31
31
  loop?: AnimationLoopMode;
32
32
  /**
33
33
  * Plays in answer to the viewer instead of on its own (exported to Flair's player):
34
- * - `click`: the first shot plays on load, and each click plays the next shot; after the last,
35
- * a click plays the second again, so a last shot that leads back to how the first ended toggles.
34
+ * - `click`: nothing plays until the viewer clicks; each click plays the next shot, and after the
35
+ * last one a click plays the first again, so a last shot that puts things back makes a toggle.
36
36
  * - `hover` / `press`: plays forward while the pointer is over (or down on) it, and back when it leaves.
37
37
  * - `scroll`: scrubs through the scene as it scrolls through the viewport.
38
38
  * The editor previews it played through; `loop` does not apply.
@@ -227,6 +227,16 @@ interface MotionMoveCommand extends MotionCommandBase {
227
227
  x: number;
228
228
  y: number;
229
229
  };
230
+ /**
231
+ * Points (document units) the centre passes through on the way, in order. The path is
232
+ * smooth (velocity carries through each point) and never overshoots a point.
233
+ */
234
+ via?: {
235
+ x: number;
236
+ y: number;
237
+ }[];
238
+ /** With `via`: `even` (default) keeps the speed steady along the path, `equal` gives each leg the same time. */
239
+ pacing?: 'even' | 'equal';
230
240
  }
231
241
  /** Turns targets about their centre, by or to an angle in degrees (clockwise). */
232
242
  interface MotionRotateCommand extends MotionCommandBase {
@@ -854,6 +864,27 @@ interface MotionMatch {
854
864
  */
855
865
  declare function matchMotionNodes(from: VectorNode, into: VectorNode): MotionMatch;
856
866
 
867
+ /** One leg of a smooth path on one axis: from → to over [start, end] (ms), eased so velocity carries across legs. */
868
+ interface MotionSmoothLeg {
869
+ start: number;
870
+ end: number;
871
+ from: number;
872
+ to: number;
873
+ ease: AnimationEasing;
874
+ }
875
+ /**
876
+ * A value passing through `values` at `times` (ms) with continuous velocity and no overshoot:
877
+ * a monotone cubic (Fritsch–Carlson) whose slopes are zero at the ends, at turning points and on
878
+ * flat stretches, so it never goes past a waypoint. Each leg is one cubic-bezier ease per axis;
879
+ * x and y ease differently, so exports to Flair or Lottie (one ease per transform) sample them.
880
+ */
881
+ declare function smoothMotionLegs(times: readonly number[], values: readonly number[]): MotionSmoothLeg[];
882
+ /** When each point of a path is reached over `duration` (ms): in proportion to distance (`even`) or a leg each (`equal`). */
883
+ declare function smoothMotionTimes(points: readonly {
884
+ x: number;
885
+ y: number;
886
+ }[], start: number, duration: number, pacing?: 'even' | 'equal'): number[];
887
+
857
888
  /**
858
889
  * A reveal clip (`wipe`, `iris`, `split`) in the units of the node's own box: 0–1 across it,
859
890
  * as SVG's `clipPathUnits="objectBoundingBox"` and Flair's clip reveals are.
@@ -1238,7 +1269,7 @@ interface FlairMotionExport {
1238
1269
  interface FlairExportOptions {
1239
1270
  /**
1240
1271
  * Play in answer to the viewer (default: the scene's `trigger`; null for none). With `click`
1241
- * each shot is a Flair step: shot 0 plays on load and each click plays the next.
1272
+ * shot n is Flair step n + 1: nothing plays until the first click, and each click plays the next shot.
1242
1273
  */
1243
1274
  trigger?: MotionTrigger | null;
1244
1275
  /** Sampling rate for changes Flair cannot express exactly (default: the scene's `fps`, else 30). */
@@ -1317,6 +1348,11 @@ interface FlairImportOptions {
1317
1348
  name?: string;
1318
1349
  /** Hold after the last shot, ms (default: the effect's `hold`, else 600). */
1319
1350
  hold?: number;
1351
+ /**
1352
+ * The motion plays on click: Flair plays step 0 together with step 1 on the first click, so
1353
+ * both become the first shot (as `toFlairSvgMotion` writes them).
1354
+ */
1355
+ clickSteps?: boolean;
1320
1356
  }
1321
1357
  /** A Flair `SvgMotion` as a scene (see the module notes for what carries over). */
1322
1358
  declare function fromFlairSvgMotion(input: FlairSvgMotion, options?: FlairImportOptions): FlairImportResult;
@@ -1645,6 +1681,11 @@ declare function describeMotionShots(scene: MotionScene): {
1645
1681
  x: number;
1646
1682
  y: number;
1647
1683
  };
1684
+ via?: {
1685
+ x: number;
1686
+ y: number;
1687
+ }[];
1688
+ pacing?: "even" | "equal";
1648
1689
  targets: string[];
1649
1690
  duration?: number;
1650
1691
  delay?: number;
@@ -1956,7 +1997,7 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1956
1997
  * A short authoring guide for a model writing vector animations with the animation editor's
1957
1998
  * tools. Static, so it caches well as part of a system prompt.
1958
1999
  */
1959
- 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 (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).\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.\n- For something the viewer plays (a button, a toggle, a step-by-step explainer), set \"trigger\" with set_animation_settings: click plays the next shot on each click (make the last shot lead back to how the first ended for a toggle), hover or press plays and rewinds, scroll scrubs.\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.";
2000
+ 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- 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.\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 (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).\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.\n- For something the viewer plays (a button, a toggle, a step-by-step explainer), set \"trigger\" with set_animation_settings: click plays the next shot on each click and then starts over (make the last shot put things back for a toggle), hover or press plays and rewinds, scroll scrubs.\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.";
1960
2001
 
1961
2002
  /**
1962
2003
  * Helpers for hand-editing UIs (React's and Svelte's command inspector, ease picker, cue picker,
@@ -2103,4 +2144,4 @@ declare function parseMotionNarration(text: string): {
2103
2144
  error: string;
2104
2145
  };
2105
2146
 
2106
- 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_CHOICES, 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, MOTION_TRIGGERS, 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 MotionEasingCurve, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFieldInput, type MotionFieldKind, type MotionFieldSpec, 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 MotionTrigger, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, defaultMotionCommand, describeMotionElements, describeMotionShots, duplicateMotionCommandEdit, fitMotionRect, fitMotionScene, fitMotionSceneEdits, flairAtLeast, flairCurveFor, flairEaseFor, formatMotionNarration, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, highlightMotionSvg, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandFields, motionCommandRepeat, motionCommandRows, motionCommandSummary, motionCueChoices, motionCueFromKey, motionCueKey, motionCueTime, motionCycleDuration, motionEaseKey, motionEasingCurve, motionEasingGuides, motionEasingPath, motionFieldEdit, motionFieldLabel, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeAtElement, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionRulerTicks, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotCueTime, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionFieldInput, parseMotionNarration, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
2147
+ 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_CHOICES, 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, MOTION_TRIGGERS, 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 MotionEasingCurve, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFieldInput, type MotionFieldKind, type MotionFieldSpec, 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 MotionSmoothLeg, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionTrigger, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, defaultMotionCommand, describeMotionElements, describeMotionShots, duplicateMotionCommandEdit, fitMotionRect, fitMotionScene, fitMotionSceneEdits, flairAtLeast, flairCurveFor, flairEaseFor, formatMotionNarration, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, highlightMotionSvg, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandFields, motionCommandRepeat, motionCommandRows, motionCommandSummary, motionCueChoices, motionCueFromKey, motionCueKey, motionCueTime, motionCycleDuration, motionEaseKey, motionEasingCurve, motionEasingGuides, motionEasingPath, motionFieldEdit, motionFieldLabel, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeAtElement, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionRulerTicks, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotCueTime, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionFieldInput, parseMotionNarration, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, smoothMotionLegs, smoothMotionTimes, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
@@ -31,8 +31,8 @@ interface MotionScene {
31
31
  loop?: AnimationLoopMode;
32
32
  /**
33
33
  * Plays in answer to the viewer instead of on its own (exported to Flair's player):
34
- * - `click`: the first shot plays on load, and each click plays the next shot; after the last,
35
- * a click plays the second again, so a last shot that leads back to how the first ended toggles.
34
+ * - `click`: nothing plays until the viewer clicks; each click plays the next shot, and after the
35
+ * last one a click plays the first again, so a last shot that puts things back makes a toggle.
36
36
  * - `hover` / `press`: plays forward while the pointer is over (or down on) it, and back when it leaves.
37
37
  * - `scroll`: scrubs through the scene as it scrolls through the viewport.
38
38
  * The editor previews it played through; `loop` does not apply.
@@ -227,6 +227,16 @@ interface MotionMoveCommand extends MotionCommandBase {
227
227
  x: number;
228
228
  y: number;
229
229
  };
230
+ /**
231
+ * Points (document units) the centre passes through on the way, in order. The path is
232
+ * smooth (velocity carries through each point) and never overshoots a point.
233
+ */
234
+ via?: {
235
+ x: number;
236
+ y: number;
237
+ }[];
238
+ /** With `via`: `even` (default) keeps the speed steady along the path, `equal` gives each leg the same time. */
239
+ pacing?: 'even' | 'equal';
230
240
  }
231
241
  /** Turns targets about their centre, by or to an angle in degrees (clockwise). */
232
242
  interface MotionRotateCommand extends MotionCommandBase {
@@ -854,6 +864,27 @@ interface MotionMatch {
854
864
  */
855
865
  declare function matchMotionNodes(from: VectorNode, into: VectorNode): MotionMatch;
856
866
 
867
+ /** One leg of a smooth path on one axis: from → to over [start, end] (ms), eased so velocity carries across legs. */
868
+ interface MotionSmoothLeg {
869
+ start: number;
870
+ end: number;
871
+ from: number;
872
+ to: number;
873
+ ease: AnimationEasing;
874
+ }
875
+ /**
876
+ * A value passing through `values` at `times` (ms) with continuous velocity and no overshoot:
877
+ * a monotone cubic (Fritsch–Carlson) whose slopes are zero at the ends, at turning points and on
878
+ * flat stretches, so it never goes past a waypoint. Each leg is one cubic-bezier ease per axis;
879
+ * x and y ease differently, so exports to Flair or Lottie (one ease per transform) sample them.
880
+ */
881
+ declare function smoothMotionLegs(times: readonly number[], values: readonly number[]): MotionSmoothLeg[];
882
+ /** When each point of a path is reached over `duration` (ms): in proportion to distance (`even`) or a leg each (`equal`). */
883
+ declare function smoothMotionTimes(points: readonly {
884
+ x: number;
885
+ y: number;
886
+ }[], start: number, duration: number, pacing?: 'even' | 'equal'): number[];
887
+
857
888
  /**
858
889
  * A reveal clip (`wipe`, `iris`, `split`) in the units of the node's own box: 0–1 across it,
859
890
  * as SVG's `clipPathUnits="objectBoundingBox"` and Flair's clip reveals are.
@@ -1238,7 +1269,7 @@ interface FlairMotionExport {
1238
1269
  interface FlairExportOptions {
1239
1270
  /**
1240
1271
  * Play in answer to the viewer (default: the scene's `trigger`; null for none). With `click`
1241
- * each shot is a Flair step: shot 0 plays on load and each click plays the next.
1272
+ * shot n is Flair step n + 1: nothing plays until the first click, and each click plays the next shot.
1242
1273
  */
1243
1274
  trigger?: MotionTrigger | null;
1244
1275
  /** Sampling rate for changes Flair cannot express exactly (default: the scene's `fps`, else 30). */
@@ -1317,6 +1348,11 @@ interface FlairImportOptions {
1317
1348
  name?: string;
1318
1349
  /** Hold after the last shot, ms (default: the effect's `hold`, else 600). */
1319
1350
  hold?: number;
1351
+ /**
1352
+ * The motion plays on click: Flair plays step 0 together with step 1 on the first click, so
1353
+ * both become the first shot (as `toFlairSvgMotion` writes them).
1354
+ */
1355
+ clickSteps?: boolean;
1320
1356
  }
1321
1357
  /** A Flair `SvgMotion` as a scene (see the module notes for what carries over). */
1322
1358
  declare function fromFlairSvgMotion(input: FlairSvgMotion, options?: FlairImportOptions): FlairImportResult;
@@ -1645,6 +1681,11 @@ declare function describeMotionShots(scene: MotionScene): {
1645
1681
  x: number;
1646
1682
  y: number;
1647
1683
  };
1684
+ via?: {
1685
+ x: number;
1686
+ y: number;
1687
+ }[];
1688
+ pacing?: "even" | "equal";
1648
1689
  targets: string[];
1649
1690
  duration?: number;
1650
1691
  delay?: number;
@@ -1956,7 +1997,7 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1956
1997
  * A short authoring guide for a model writing vector animations with the animation editor's
1957
1998
  * tools. Static, so it caches well as part of a system prompt.
1958
1999
  */
1959
- 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 (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).\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.\n- For something the viewer plays (a button, a toggle, a step-by-step explainer), set \"trigger\" with set_animation_settings: click plays the next shot on each click (make the last shot lead back to how the first ended for a toggle), hover or press plays and rewinds, scroll scrubs.\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.";
2000
+ 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- 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.\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 (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).\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.\n- For something the viewer plays (a button, a toggle, a step-by-step explainer), set \"trigger\" with set_animation_settings: click plays the next shot on each click and then starts over (make the last shot put things back for a toggle), hover or press plays and rewinds, scroll scrubs.\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.";
1960
2001
 
1961
2002
  /**
1962
2003
  * Helpers for hand-editing UIs (React's and Svelte's command inspector, ease picker, cue picker,
@@ -2103,4 +2144,4 @@ declare function parseMotionNarration(text: string): {
2103
2144
  error: string;
2104
2145
  };
2105
2146
 
2106
- 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_CHOICES, 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, MOTION_TRIGGERS, 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 MotionEasingCurve, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFieldInput, type MotionFieldKind, type MotionFieldSpec, 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 MotionTrigger, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, defaultMotionCommand, describeMotionElements, describeMotionShots, duplicateMotionCommandEdit, fitMotionRect, fitMotionScene, fitMotionSceneEdits, flairAtLeast, flairCurveFor, flairEaseFor, formatMotionNarration, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, highlightMotionSvg, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandFields, motionCommandRepeat, motionCommandRows, motionCommandSummary, motionCueChoices, motionCueFromKey, motionCueKey, motionCueTime, motionCycleDuration, motionEaseKey, motionEasingCurve, motionEasingGuides, motionEasingPath, motionFieldEdit, motionFieldLabel, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeAtElement, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionRulerTicks, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotCueTime, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionFieldInput, parseMotionNarration, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
2147
+ 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_CHOICES, 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, MOTION_TRIGGERS, 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 MotionEasingCurve, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFieldInput, type MotionFieldKind, type MotionFieldSpec, 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 MotionSmoothLeg, type MotionSplitOrientation, type MotionStyleCommand, type MotionSvgOptions, type MotionTeeterCommand, type MotionTimeline, type MotionTrigger, type MotionValue, type MotionWaitCommand, type MotionWaveCommand, type MotionWiggleCommand, activeSegment, applyMotionControlValues, applyMotionEdits, checkMotionScene, commandShapeProblems, compileMotionScene, createAnimationEditorToolProvider, createMotionScene, defaultMotionCommand, describeMotionElements, describeMotionShots, duplicateMotionCommandEdit, fitMotionRect, fitMotionScene, fitMotionSceneEdits, flairAtLeast, flairCurveFor, flairEaseFor, formatMotionNarration, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, highlightMotionSvg, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionBeatAt, motionBeatTime, motionClipAttrs, motionCommandDuration, motionCommandFields, motionCommandRepeat, motionCommandRows, motionCommandSummary, motionCueChoices, motionCueFromKey, motionCueKey, motionCueTime, motionCycleDuration, motionEaseKey, motionEasingCurve, motionEasingGuides, motionEasingPath, motionFieldEdit, motionFieldLabel, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionGhosts, motionIssue, motionIssueKey, motionIssueLine, motionMovingIds, motionNodeAtElement, motionNodeClips, motionNodeOffset, motionPath, motionPathLength, motionPathsSvg, motionRestValue, motionRevealClip, motionRevealPadding, motionRulerTicks, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionShotCueTime, motionShotTimeScale, motionStageSvg, motionTrackKey, namedWithSuggestion, parseMotionFieldInput, parseMotionNarration, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, smoothMotionLegs, smoothMotionTimes, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, toLottie, trackValue, withMotionLayers, withViewBox };
@@ -222,7 +222,7 @@ var COMMAND_KEYS = {
222
222
  enter: [...BASE_KEYS, "style", "direction", "distance", "scale", "shape", "orientation"],
223
223
  exit: [...BASE_KEYS, "style", "direction", "distance"],
224
224
  draw: [...BASE_KEYS, "from", "to"],
225
- move: [...BASE_KEYS, "by", "to", "blend"],
225
+ move: [...BASE_KEYS, "by", "to", "via", "pacing", "blend"],
226
226
  rotate: [...BASE_KEYS, "by", "to", "blend"],
227
227
  scale: [...BASE_KEYS, "to", "geometric", "blend"],
228
228
  fade: [...BASE_KEYS, "to"],
@@ -303,6 +303,9 @@ function commandShapeProblems(command2, where) {
303
303
  if (command2.by !== void 0 && command2.to !== void 0) p(`give "by" or "to", not both.`);
304
304
  if (command2.by !== void 0 && !isPoint(command2.by)) p(`"by" must be { "x": number, "y": number }.`);
305
305
  if (command2.to !== void 0 && !isPoint(command2.to)) p(`"to" must be { "x": number, "y": number }.`);
306
+ if (command2.via !== void 0 && !(Array.isArray(command2.via) && command2.via.length > 0 && command2.via.length <= 64 && command2.via.every(isPoint))) p(`"via" must be a list of 1 to 64 points { "x": number, "y": number } (document units).`);
307
+ if (command2.pacing !== void 0 && command2.pacing !== "even" && command2.pacing !== "equal") p(`"pacing" must be "even" (steady speed) or "equal" (each leg the same time).`);
308
+ if (command2.pacing !== void 0 && command2.via === void 0) p(`"pacing" goes with "via".`);
306
309
  break;
307
310
  case "rotate":
308
311
  if (command2.by === void 0 && command2.to === void 0) p(`give "by" or "to" (degrees).`);
@@ -503,6 +506,45 @@ function matchMotionNodes(from, into) {
503
506
  return out;
504
507
  }
505
508
 
509
+ // src/animation-editor/smooth.ts
510
+ function smoothMotionLegs(times, values) {
511
+ const n = values.length;
512
+ const slope = new Array(n).fill(0);
513
+ const secant = (i) => (values[i + 1] - values[i]) / Math.max(1e-9, times[i + 1] - times[i]);
514
+ for (let i = 1; i < n - 1; i++) {
515
+ const a = secant(i - 1);
516
+ const b = secant(i);
517
+ if (a * b <= 0) continue;
518
+ const ha = times[i] - times[i - 1];
519
+ const hb = times[i + 1] - times[i];
520
+ slope[i] = 3 * (ha + hb) / ((2 * hb + ha) / a + (hb + 2 * ha) / b);
521
+ }
522
+ const legs = [];
523
+ for (let i = 0; i + 1 < n; i++) {
524
+ const dt = times[i + 1] - times[i];
525
+ const dv = values[i + 1] - values[i];
526
+ if (Math.abs(dv) < 1e-9) {
527
+ legs.push({ start: times[i], end: times[i + 1], from: values[i], to: values[i + 1], ease: "linear" });
528
+ continue;
529
+ }
530
+ const y1 = slope[i] * dt / (3 * dv);
531
+ const y2 = 1 - slope[i + 1] * dt / (3 * dv);
532
+ legs.push({ start: times[i], end: times[i + 1], from: values[i], to: values[i + 1], ease: { type: "cubic-bezier", x1: 1 / 3, y1, x2: 2 / 3, y2 } });
533
+ }
534
+ return legs;
535
+ }
536
+ function smoothMotionTimes(points, start, duration, pacing = "even") {
537
+ const legs = points.slice(1).map((p, i) => pacing === "equal" ? 1 : Math.hypot(p.x - points[i].x, p.y - points[i].y));
538
+ const total = legs.reduce((a, b) => a + b, 0) || legs.length || 1;
539
+ const times = [start];
540
+ let acc = 0;
541
+ for (const l of legs) {
542
+ acc += total > 0 ? l : 1;
543
+ times.push(start + duration * acc / total);
544
+ }
545
+ return times;
546
+ }
547
+
506
548
  // src/animation-editor/compile.ts
507
549
  var indexCache = /* @__PURE__ */ new WeakMap();
508
550
  function indexMotionNodes(nodes) {
@@ -764,6 +806,7 @@ function motionCycleDuration(command2, scene, target) {
764
806
  const ms = length * MOTION_DRAW_MS_PER_UNIT;
765
807
  return Math.round(Math.min(MOTION_DRAW_RANGE[1], Math.max(MOTION_DRAW_RANGE[0], ms)));
766
808
  }
809
+ if (command2.type === "move" && command2.via?.length) return MOTION_DEFAULT_DURATIONS.move + 400 * command2.via.length;
767
810
  return MOTION_DEFAULT_DURATIONS[command2.type];
768
811
  }
769
812
  var MOTION_EASE_OUT = { type: "cubic-bezier", x1: 1 / 3, y1: 1, x2: 2 / 3, y2: 1 };
@@ -962,6 +1005,20 @@ function compileMotionScene(scene) {
962
1005
  dx = target.x - (entry.center.x + num("x"));
963
1006
  dy = target.y - (entry.center.y + num("y"));
964
1007
  }
1008
+ if (command2.via?.length) {
1009
+ const inv = invertMatrix(entry.parentWorld);
1010
+ const x0 = num("x");
1011
+ const y0 = num("y");
1012
+ const add = command2.blend === "add";
1013
+ const offsets = [{ x: 0, y: 0 }, ...command2.via.map((p) => applyMatrix(inv, p)).map((p) => ({ x: p.x - entry.center.x - x0, y: p.y - entry.center.y - y0 })), { x: dx, y: dy }];
1014
+ const times = smoothMotionTimes(offsets, start, end - start, command2.pacing ?? "even");
1015
+ for (const axis of ["x", "y"]) {
1016
+ const base = add ? 0 : axis === "x" ? x0 : y0;
1017
+ for (const leg of smoothMotionLegs(times, offsets.map((o) => o[axis])))
1018
+ seg(axis, base + leg.to, leg.ease, leg.start, leg.end, { from: base + leg.from, ...add ? { blend: "add" } : {} });
1019
+ }
1020
+ break;
1021
+ }
965
1022
  if (command2.blend === "add") {
966
1023
  seg("x", dx, e, start, end, { from: 0, blend: "add" });
967
1024
  seg("y", dy, e, start, end, { from: 0, blend: "add" });
@@ -1501,7 +1558,7 @@ function checkMotionScene(scene) {
1501
1558
  if (scene.trigger) {
1502
1559
  if ((scene.loop ?? "none") !== "none") issues.push(motionIssue("trigger-loop", `The scene plays on ${scene.trigger}, so its "${scene.loop}" loop does not apply; set loop to "none" or remove the trigger.`, {}));
1503
1560
  if (scene.trigger === "click") {
1504
- if (scene.shots.length === 1) issues.push(motionIssue("click-one-shot", "The scene plays on click but has one shot, so every click replays it from the start; split it into shots (the first plays on load, each click plays the next).", { shot: scene.shots[0].id }));
1561
+ if (scene.shots.length === 1) issues.push(motionIssue("click-one-shot", "The scene plays on click but has one shot, so every click replays it from the start; split it into shots (each click plays the next one).", { shot: scene.shots[0].id }));
1505
1562
  scene.shots.forEach((shot, i) => {
1506
1563
  if (i > 0 && shot.at) issues.push(motionIssue("click-cue", `Shot "${shot.id}" is cued, but on click a shot starts when it is clicked to; remove its "at" or the trigger.`, { shot: shot.id }));
1507
1564
  });
@@ -2065,7 +2122,7 @@ function toFlairSvgMotion(scene, options = {}) {
2065
2122
  animate.sort((x, y) => (x.delay ?? 0) - (y.delay ?? 0));
2066
2123
  const lastChange = animate.reduce((m, a) => Math.max(m, (a.delay ?? 0) + (a.ms ?? 0)), 0);
2067
2124
  const trigger = options.trigger === null ? void 0 : options.trigger ?? scene.trigger;
2068
- if (trigger === "click" && timeline.shots.length > 1) {
2125
+ if (trigger === "click" && timeline.shots.length) {
2069
2126
  for (const a of animate) {
2070
2127
  const at = a.delay ?? 0;
2071
2128
  let step = 0;
@@ -2075,7 +2132,7 @@ function toFlairSvgMotion(scene, options = {}) {
2075
2132
  const delay = round2(at - timeline.shots[step].start);
2076
2133
  delete a.delay;
2077
2134
  if (delay > 0) a.delay = delay;
2078
- if (step > 0) a.step = step;
2135
+ a.step = step + 1;
2079
2136
  }
2080
2137
  animate.sort((x, y) => (x.step ?? 0) - (y.step ?? 0) || (x.delay ?? 0) - (y.delay ?? 0));
2081
2138
  }
@@ -2260,7 +2317,8 @@ function fromFlairSvgMotion(input, options = {}) {
2260
2317
  const nodes = indexMotionNodes(artwork.nodes);
2261
2318
  const byStep = /* @__PURE__ */ new Map();
2262
2319
  for (const anim of motion.animate ?? []) {
2263
- const step = Math.max(0, Math.floor(anim.step ?? 0));
2320
+ const raw = Math.max(0, Math.floor(anim.step ?? 0));
2321
+ const step = options.clickSteps ? Math.max(1, raw) : raw;
2264
2322
  if (!byStep.has(step)) byStep.set(step, []);
2265
2323
  byStep.get(step).push(anim);
2266
2324
  }
@@ -2311,7 +2369,7 @@ function presetColor(value) {
2311
2369
  }
2312
2370
  function fromFlairVectorEffect(effect, options = {}) {
2313
2371
  if (!effect || effect.kind !== "vector" || !effect.motion) throw new Error('Give a Flair vector effect: { kind: "vector", id, name, motion: { svg, animate } }.');
2314
- const result = fromFlairSvgMotion(effect.motion, { name: options.name ?? effect.id ?? effect.name, hold: options.hold ?? effect.hold });
2372
+ const result = fromFlairSvgMotion(effect.motion, { name: options.name ?? effect.id ?? effect.name, hold: options.hold ?? effect.hold, ...effect.trigger === "click" ? { clickSteps: true } : {} });
2315
2373
  const names = new Set((result.scene.controls ?? []).map((c) => c.name));
2316
2374
  const presets = (effect.presets ?? []).map((p) => ({ name: p.name, values: Object.fromEntries(Object.entries(p.values).flatMap(([k, v]) => names.has(k) && presetColor(v) ? [[k, presetColor(v)]] : [])) })).filter((p) => Object.keys(p.values).length);
2317
2375
  const scene = { ...result.scene, ...effect.description ? { description: effect.description } : {}, ...presets.length ? { presets } : {}, ...effect.trigger ? { trigger: effect.trigger } : {} };
@@ -2936,7 +2994,7 @@ var MOTION_COMMAND_SCHEMA = {
2936
2994
  distance: { type: "number", description: "For slide: document units (default 40)." }
2937
2995
  }),
2938
2996
  command("draw", "Draws strokes on (or off) to a fraction of their length.", { to: FRACTION("Fraction drawn at the end (default 1)."), from: FRACTION("Fraction drawn at the start (default: what is drawn now).") }),
2939
- command("move", "Moves targets by an offset or so their centre lands on a point.", { blend: BLEND("replace"), by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
2997
+ command("move", "Moves targets by an offset or so their centre lands on a point.", { via: { type: "array", items: POINT("A point the centre passes through."), minItems: 1, maxItems: 64, description: 'Waypoints (document units) the centre passes through on the way to "to" or "by", smoothly (velocity carries through each) and without overshooting any. Default length 700 ms + 400 per waypoint.' }, pacing: { type: "string", enum: ["even", "equal"], description: "With via: even (default) keeps the speed steady along the path; equal gives each stretch the same time." }, blend: BLEND("replace"), by: POINT("Offset in document units."), to: POINT("Point (document coordinates) for the centre.") }),
2940
2998
  command("rotate", "Turns targets about their centre (degrees, clockwise).", { blend: BLEND("replace"), by: { type: "number", description: "Degrees to turn by." }, to: { type: "number", description: "Angle to turn to, in degrees from rest." } }),
2941
2999
  command("scale", "Scales targets about their centre (1 = rest size).", { blend: BLEND("replace"), to: { description: "A factor, or { x, y } factors.", anyOf: [{ type: "number" }, POINT("Factors per axis.")] }, geometric: { type: "boolean", description: "Grow by a steady factor, which reads as an even zoom (default false)." } }, ["to"]),
2942
3000
  command("fade", "Changes opacity.", { to: FRACTION("Opacity at the end (0\u20131).") }, ["to"]),
@@ -3025,7 +3083,7 @@ var SETTINGS = {
3025
3083
  loop: { type: "string", enum: ["none", "loop", "ping-pong"], description: "Playback past the end (default none)." },
3026
3084
  trigger: {
3027
3085
  anyOf: [{ type: "string", enum: ["click", "hover", "press", "scroll"] }, { type: "null" }],
3028
- description: "Play in answer to the viewer when exported to Flair: click (the first shot plays on load, each click plays the next shot), hover or press (plays while the pointer is over or down, rewinds when it leaves), scroll (scrubs as it scrolls past). null plays on its own (default). The editor previews it played through."
3086
+ description: "Play in answer to the viewer when exported to Flair: click (nothing plays until a click; each click plays the next shot, then the first again), hover or press (plays while the pointer is over or down, rewinds when it leaves), scroll (scrubs as it scrolls past). null plays on its own (default). The editor previews it played through."
3029
3087
  },
3030
3088
  fps: { type: "number", minimum: 1, maximum: 120, description: "Sampling rate for exports (default 30)." },
3031
3089
  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).' },
@@ -3606,6 +3664,7 @@ Commands
3606
3664
  - enter (fade, pop, grow, slide, draw) and exit (fade, shrink, slide, undraw) bring things in and out.
3607
3665
  - 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.
3608
3666
  - 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.
3667
+ - 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.
3609
3668
  - enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
3610
3669
  - 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).
3611
3670
  - 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).
@@ -3621,7 +3680,7 @@ Working method
3621
3680
  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).
3622
3681
  2. Plan a few shots, each one idea. Add them with insert_shots (add_commands, update_command, move_command and delete_commands refine a shot), or edit_animation to make several edits as one step (dry_run first when unsure).
3623
3682
  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.
3624
- - For something the viewer plays (a button, a toggle, a step-by-step explainer), set "trigger" with set_animation_settings: click plays the next shot on each click (make the last shot lead back to how the first ended for a toggle), hover or press plays and rewinds, scroll scrubs.
3683
+ - For something the viewer plays (a button, a toggle, a step-by-step explainer), set "trigger" with set_animation_settings: click plays the next shot on each click and then starts over (make the last shot put things back for a toggle), hover or press plays and rewinds, scroll scrubs.
3625
3684
  4. To make a reusable Flair effect, set_animation_controls (colour controls stand for every use of their default colour) and export_flair_effect.
3626
3685
 
3627
3686
  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.`;
@@ -4011,6 +4070,8 @@ export {
4011
4070
  segmentProgress,
4012
4071
  segmentValue,
4013
4072
  shotShapeProblems,
4073
+ smoothMotionLegs,
4074
+ smoothMotionTimes,
4014
4075
  suggestName,
4015
4076
  toFlairEffectDocument,
4016
4077
  toFlairSvgMotion,