@pixerate/editor 0.60.0 → 0.61.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.
@@ -223,6 +223,17 @@ interface MotionMorphCommand extends MotionCommandBase {
223
223
  type: 'morph';
224
224
  into: string;
225
225
  }
226
+ /**
227
+ * Smart Animate: tweens a group into another group (`into`, usually hidden), part by part. Each
228
+ * part of the target takes the shape, paint and place of its partner in `into`, paired by name,
229
+ * then by id (`card-a-title` ↔ `card-b-title`), then by kind in order. Parts with no partner fade
230
+ * out; parts of `into` with no partner do not appear (`check` lists them).
231
+ */
232
+ interface MotionMatchCommand extends MotionCommandBase {
233
+ type: 'match';
234
+ /** Id of the group (or node) to become. */
235
+ into: string;
236
+ }
226
237
  /** Moves targets along a path node, centre on the path. */
227
238
  interface MotionFollowCommand extends MotionCommandBase {
228
239
  type: 'follow';
@@ -346,7 +357,7 @@ interface MotionWaitCommand {
346
357
  with?: 'after' | 'previous';
347
358
  note?: string;
348
359
  }
349
- type MotionCommand = MotionEnterCommand | MotionExitCommand | MotionDrawCommand | MotionMoveCommand | MotionRotateCommand | MotionScaleCommand | MotionFadeCommand | MotionStyleCommand | MotionMorphCommand | MotionFollowCommand | MotionPulseCommand | MotionShakeCommand | MotionTeeterCommand | MotionWaveCommand | MotionWiggleCommand | MotionColorPulseCommand | MotionEffectCommand | MotionWaitCommand;
360
+ type MotionCommand = MotionEnterCommand | MotionExitCommand | MotionDrawCommand | MotionMoveCommand | MotionRotateCommand | MotionScaleCommand | MotionFadeCommand | MotionStyleCommand | MotionMorphCommand | MotionMatchCommand | MotionFollowCommand | MotionPulseCommand | MotionShakeCommand | MotionTeeterCommand | MotionWaveCommand | MotionWiggleCommand | MotionColorPulseCommand | MotionEffectCommand | MotionWaitCommand;
350
361
  type MotionCommandType = MotionCommand['type'];
351
362
  declare const MOTION_COMMAND_TYPES: readonly MotionCommandType[];
352
363
  declare const MOTION_ENTER_STYLES: readonly MotionEnterStyle[];
@@ -425,6 +436,14 @@ declare const MOTION_ISSUE_CODES: {
425
436
  readonly severity: "error";
426
437
  readonly wording: "A morph names a shape the artwork does not have.";
427
438
  };
439
+ readonly 'match-self': {
440
+ readonly severity: "error";
441
+ readonly wording: "A match turns a group into itself or into a group it holds (or that holds it).";
442
+ };
443
+ readonly 'match-unpaired': {
444
+ readonly severity: "note";
445
+ readonly wording: "Parts of a match's \"into\" group have no partner in the target, so they do not appear.";
446
+ };
428
447
  readonly 'unknown-effect': {
429
448
  readonly severity: "error";
430
449
  readonly wording: "An effect command names an effect the node does not have.";
@@ -730,6 +749,23 @@ declare function compileMotionScene(scene: MotionScene): MotionTimeline;
730
749
  /** The shot playing at `time` (the last one at the end). */
731
750
  declare function motionShotAt(timeline: MotionTimeline, time: number): MotionShotTiming | undefined;
732
751
 
752
+ /** How the parts of one node pair with the parts of another for a `match` (Smart Animate). */
753
+ interface MotionMatch {
754
+ /** `[from id, into id]`: each part of the target and the part of `into` it becomes. */
755
+ pairs: [string, string][];
756
+ /** Parts of the target with no partner (they fade out). */
757
+ unpairedFrom: string[];
758
+ /** Parts of `into` with no partner (they cannot appear). */
759
+ unpairedInto: string[];
760
+ }
761
+ /**
762
+ * Pairs the parts of `from` with the parts of `into`, each part used once, in passes: the same
763
+ * `name`, then the same id once each group's own id is taken out, then the same kind in order.
764
+ * Paired groups pair their own parts the same way, so a part pairs with a part, never a group
765
+ * with a path. Two paths (or a path and a group with no children) pair whole.
766
+ */
767
+ declare function matchMotionNodes(from: VectorNode, into: VectorNode): MotionMatch;
768
+
733
769
  /**
734
770
  * A reveal clip (`wipe`, `iris`, `split`) in the units of the node's own box: 0–1 across it,
735
771
  * as SVG's `clipPathUnits="objectBoundingBox"` and Flair's clip reveals are.
@@ -1435,6 +1471,23 @@ declare function describeMotionShots(scene: MotionScene): {
1435
1471
  repeatMode?: "loop" | "mirror" | "offset";
1436
1472
  steps?: number;
1437
1473
  index: number;
1474
+ } | {
1475
+ flairPrimitive?: string | undefined;
1476
+ start: number;
1477
+ end: number;
1478
+ type: "match";
1479
+ into: string;
1480
+ targets: string[];
1481
+ duration?: number;
1482
+ delay?: number;
1483
+ stagger?: number;
1484
+ with?: "after" | "previous";
1485
+ ease?: AnimationEasing;
1486
+ note?: string;
1487
+ repeat?: number;
1488
+ repeatMode?: "loop" | "mirror" | "offset";
1489
+ steps?: number;
1490
+ index: number;
1438
1491
  } | {
1439
1492
  flairPrimitive?: string | undefined;
1440
1493
  start: number;
@@ -1622,6 +1675,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1622
1675
  * A short authoring guide for a model writing vector animations with the animation editor's
1623
1676
  * tools. Static, so it caches well as part of a system prompt.
1624
1677
  */
1625
- 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); 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- \"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. 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.";
1678
+ 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- \"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. 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.";
1626
1679
 
1627
- 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, 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_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMorphCommand, type MotionMoveCommand, 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, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
1680
+ 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, 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_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, 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, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
@@ -223,6 +223,17 @@ interface MotionMorphCommand extends MotionCommandBase {
223
223
  type: 'morph';
224
224
  into: string;
225
225
  }
226
+ /**
227
+ * Smart Animate: tweens a group into another group (`into`, usually hidden), part by part. Each
228
+ * part of the target takes the shape, paint and place of its partner in `into`, paired by name,
229
+ * then by id (`card-a-title` ↔ `card-b-title`), then by kind in order. Parts with no partner fade
230
+ * out; parts of `into` with no partner do not appear (`check` lists them).
231
+ */
232
+ interface MotionMatchCommand extends MotionCommandBase {
233
+ type: 'match';
234
+ /** Id of the group (or node) to become. */
235
+ into: string;
236
+ }
226
237
  /** Moves targets along a path node, centre on the path. */
227
238
  interface MotionFollowCommand extends MotionCommandBase {
228
239
  type: 'follow';
@@ -346,7 +357,7 @@ interface MotionWaitCommand {
346
357
  with?: 'after' | 'previous';
347
358
  note?: string;
348
359
  }
349
- type MotionCommand = MotionEnterCommand | MotionExitCommand | MotionDrawCommand | MotionMoveCommand | MotionRotateCommand | MotionScaleCommand | MotionFadeCommand | MotionStyleCommand | MotionMorphCommand | MotionFollowCommand | MotionPulseCommand | MotionShakeCommand | MotionTeeterCommand | MotionWaveCommand | MotionWiggleCommand | MotionColorPulseCommand | MotionEffectCommand | MotionWaitCommand;
360
+ type MotionCommand = MotionEnterCommand | MotionExitCommand | MotionDrawCommand | MotionMoveCommand | MotionRotateCommand | MotionScaleCommand | MotionFadeCommand | MotionStyleCommand | MotionMorphCommand | MotionMatchCommand | MotionFollowCommand | MotionPulseCommand | MotionShakeCommand | MotionTeeterCommand | MotionWaveCommand | MotionWiggleCommand | MotionColorPulseCommand | MotionEffectCommand | MotionWaitCommand;
350
361
  type MotionCommandType = MotionCommand['type'];
351
362
  declare const MOTION_COMMAND_TYPES: readonly MotionCommandType[];
352
363
  declare const MOTION_ENTER_STYLES: readonly MotionEnterStyle[];
@@ -425,6 +436,14 @@ declare const MOTION_ISSUE_CODES: {
425
436
  readonly severity: "error";
426
437
  readonly wording: "A morph names a shape the artwork does not have.";
427
438
  };
439
+ readonly 'match-self': {
440
+ readonly severity: "error";
441
+ readonly wording: "A match turns a group into itself or into a group it holds (or that holds it).";
442
+ };
443
+ readonly 'match-unpaired': {
444
+ readonly severity: "note";
445
+ readonly wording: "Parts of a match's \"into\" group have no partner in the target, so they do not appear.";
446
+ };
428
447
  readonly 'unknown-effect': {
429
448
  readonly severity: "error";
430
449
  readonly wording: "An effect command names an effect the node does not have.";
@@ -730,6 +749,23 @@ declare function compileMotionScene(scene: MotionScene): MotionTimeline;
730
749
  /** The shot playing at `time` (the last one at the end). */
731
750
  declare function motionShotAt(timeline: MotionTimeline, time: number): MotionShotTiming | undefined;
732
751
 
752
+ /** How the parts of one node pair with the parts of another for a `match` (Smart Animate). */
753
+ interface MotionMatch {
754
+ /** `[from id, into id]`: each part of the target and the part of `into` it becomes. */
755
+ pairs: [string, string][];
756
+ /** Parts of the target with no partner (they fade out). */
757
+ unpairedFrom: string[];
758
+ /** Parts of `into` with no partner (they cannot appear). */
759
+ unpairedInto: string[];
760
+ }
761
+ /**
762
+ * Pairs the parts of `from` with the parts of `into`, each part used once, in passes: the same
763
+ * `name`, then the same id once each group's own id is taken out, then the same kind in order.
764
+ * Paired groups pair their own parts the same way, so a part pairs with a part, never a group
765
+ * with a path. Two paths (or a path and a group with no children) pair whole.
766
+ */
767
+ declare function matchMotionNodes(from: VectorNode, into: VectorNode): MotionMatch;
768
+
733
769
  /**
734
770
  * A reveal clip (`wipe`, `iris`, `split`) in the units of the node's own box: 0–1 across it,
735
771
  * as SVG's `clipPathUnits="objectBoundingBox"` and Flair's clip reveals are.
@@ -1435,6 +1471,23 @@ declare function describeMotionShots(scene: MotionScene): {
1435
1471
  repeatMode?: "loop" | "mirror" | "offset";
1436
1472
  steps?: number;
1437
1473
  index: number;
1474
+ } | {
1475
+ flairPrimitive?: string | undefined;
1476
+ start: number;
1477
+ end: number;
1478
+ type: "match";
1479
+ into: string;
1480
+ targets: string[];
1481
+ duration?: number;
1482
+ delay?: number;
1483
+ stagger?: number;
1484
+ with?: "after" | "previous";
1485
+ ease?: AnimationEasing;
1486
+ note?: string;
1487
+ repeat?: number;
1488
+ repeatMode?: "loop" | "mirror" | "offset";
1489
+ steps?: number;
1490
+ index: number;
1438
1491
  } | {
1439
1492
  flairPrimitive?: string | undefined;
1440
1493
  start: number;
@@ -1622,6 +1675,6 @@ declare function createAnimationEditorToolProvider(controller: AnimationEditorCo
1622
1675
  * A short authoring guide for a model writing vector animations with the animation editor's
1623
1676
  * tools. Static, so it caches well as part of a system prompt.
1624
1677
  */
1625
- 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); 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- \"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. 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.";
1678
+ 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- \"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. 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.";
1626
1679
 
1627
- 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, 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_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMorphCommand, type MotionMoveCommand, 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, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
1680
+ 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, 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_SPLIT_ORIENTATIONS, type MotionCamera, type MotionClipShape, type MotionColorPulseCommand, type MotionCommand, type MotionCommandBase, type MotionCommandTiming, type MotionCommandType, type MotionControl, type MotionDirection, type MotionDrawCommand, type MotionEdit, type MotionEditOp, type MotionEditResult, type MotionEffectCommand, type MotionEnterCommand, type MotionEnterStyle, type MotionExitCommand, type MotionExitStyle, type MotionFadeCommand, type MotionFollowCommand, type MotionFrame, type MotionFrameOptions, type MotionIrisShape, type MotionIssue, type MotionIssueCode, type MotionIssueSeverity, type MotionMatch, type MotionMatchCommand, type MotionMorphCommand, type MotionMoveCommand, 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, flairAtLeast, flairCurveFor, flairEaseFor, freshShotId, fromFlairSvgMotion, fromFlairVectorEffect, indexMotionNodes, interpolateMotionValue, isMotionCommand, isMotionShot, matchMotionNodes, motionAddTrackKey, motionClipAttrs, motionCommandDuration, motionCommandRepeat, motionCycleDuration, motionFlairPrimitive, motionFollowRoute, motionFrameAt, motionFrameToSvg, motionIssue, motionIssueKey, motionIssueLine, motionNodeClips, motionNodeOffset, motionPathLength, motionRestValue, motionRevealClip, motionRevealPadding, motionSceneShapeProblems, motionSceneToJson, motionShotAt, motionTrackKey, namedWithSuggestion, parseMotionScene, resolveMotionCamera, segmentProgress, segmentValue, shotShapeProblems, suggestName, toFlairEffectDocument, toFlairSvgMotion, toFlairVectorEffect, trackValue, withMotionLayers, withViewBox };
@@ -63,6 +63,7 @@ var MOTION_COMMAND_TYPES = [
63
63
  "fade",
64
64
  "style",
65
65
  "morph",
66
+ "match",
66
67
  "follow",
67
68
  "pulse",
68
69
  "shake",
@@ -89,6 +90,7 @@ var MOTION_DEFAULT_DURATIONS = {
89
90
  fade: 400,
90
91
  style: 500,
91
92
  morph: 900,
93
+ match: 900,
92
94
  follow: 1400,
93
95
  pulse: 600,
94
96
  shake: 500,
@@ -133,6 +135,8 @@ var MOTION_ISSUE_CODES = {
133
135
  "not-a-path": { severity: "error", wording: "A follow path must be a path node, not a group." },
134
136
  "reveal-no-box": { severity: "warning", wording: "A wipe, iris or split cannot clip a node with no width or height (a lone straight line), so it fades instead." },
135
137
  "unknown-shape": { severity: "error", wording: "A morph names a shape the artwork does not have." },
138
+ "match-self": { severity: "error", wording: "A match turns a group into itself or into a group it holds (or that holds it)." },
139
+ "match-unpaired": { severity: "note", wording: `Parts of a match's "into" group have no partner in the target, so they do not appear.` },
136
140
  "unknown-effect": { severity: "error", wording: "An effect command names an effect the node does not have." },
137
141
  "unknown-effect-param": { severity: "error", wording: "An effect command names a parameter that is not a number of that effect." },
138
142
  "unknown-frame": { severity: "error", wording: "A camera frames a node the artwork does not have." },
@@ -216,6 +220,7 @@ var COMMAND_KEYS = {
216
220
  fade: [...BASE_KEYS, "to"],
217
221
  style: [...BASE_KEYS, "fill", "stroke", "strokeWidth"],
218
222
  morph: [...BASE_KEYS, "into"],
223
+ match: [...BASE_KEYS, "into"],
219
224
  follow: [...BASE_KEYS, "path", "rotate", "from", "to"],
220
225
  pulse: [...BASE_KEYS, "amount", "count", "blend"],
221
226
  shake: [...BASE_KEYS, "amount", "count", "axis", "blend"],
@@ -311,7 +316,8 @@ function commandShapeProblems(command2, where) {
311
316
  if (command2.strokeWidth !== void 0 && !(isNum(command2.strokeWidth) && command2.strokeWidth >= 0)) p(`"strokeWidth" must be a number \u2265 0.`);
312
317
  break;
313
318
  case "morph":
314
- if (typeof command2.into !== "string" || !command2.into) p(`"into" must be the id of a node whose shape to take.`);
319
+ case "match":
320
+ if (typeof command2.into !== "string" || !command2.into) p(`"into" must be the id of a ${type === "match" ? "group to become" : "node whose shape to take"}.`);
315
321
  break;
316
322
  case "follow":
317
323
  if (typeof command2.path !== "string" || !command2.path) p(`"path" must be the id of a path node.`);
@@ -420,6 +426,53 @@ function motionSceneToJson(scene, space = 2) {
420
426
  var isMotionShot = (shot) => shotShapeProblems(shot, "Shot").length === 0;
421
427
  var isMotionCommand = (command2) => commandShapeProblems(command2, "Command").length === 0;
422
428
 
429
+ // src/animation-editor/match.ts
430
+ var SEPARATOR = /[-_/.:]/;
431
+ function localId(id, groupId) {
432
+ if (groupId && id.includes(groupId)) return id.split(groupId).join("").replace(/^[-_/.:]+|[-_/.:]+$/g, "");
433
+ const parts = id.split(SEPARATOR);
434
+ return parts[parts.length - 1] ?? id;
435
+ }
436
+ function matchMotionNodes(from, into) {
437
+ const out = { pairs: [], unpairedFrom: [], unpairedInto: [] };
438
+ const visit = (a, b) => {
439
+ const aKids = a.type === "group" ? a.children : [];
440
+ const bKids = b.type === "group" ? b.children : [];
441
+ if (!aKids.length || !bKids.length) {
442
+ out.pairs.push([a.id, b.id]);
443
+ return;
444
+ }
445
+ const left = new Set(aKids);
446
+ const right = new Set(bKids);
447
+ const pair = (key) => {
448
+ for (const x of [...left]) {
449
+ const k = key(x, a);
450
+ if (!k) continue;
451
+ const y = [...right].find((n) => key(n, b) === k);
452
+ if (!y) continue;
453
+ left.delete(x);
454
+ right.delete(y);
455
+ if (x.type === "group" && y.type === "group") visit(x, y);
456
+ else out.pairs.push([x.id, y.id]);
457
+ }
458
+ };
459
+ pair((n) => n.name?.trim() || void 0);
460
+ pair((n, g) => localId(n.id, g.id) || void 0);
461
+ for (const x of [...left]) {
462
+ const y = [...right].find((n) => n.type === x.type);
463
+ if (!y) continue;
464
+ left.delete(x);
465
+ right.delete(y);
466
+ if (x.type === "group" && y.type === "group") visit(x, y);
467
+ else out.pairs.push([x.id, y.id]);
468
+ }
469
+ out.unpairedFrom.push(...[...left].map((n) => n.id));
470
+ out.unpairedInto.push(...[...right].map((n) => n.id));
471
+ };
472
+ visit(from, into);
473
+ return out;
474
+ }
475
+
423
476
  // src/animation-editor/compile.ts
424
477
  var indexCache = /* @__PURE__ */ new WeakMap();
425
478
  function indexMotionNodes(nodes) {
@@ -886,6 +939,15 @@ function compileMotionScene(scene) {
886
939
  case "morph":
887
940
  seg("shape", command2.into, ease ?? MOTION_EASE_IN_OUT);
888
941
  break;
942
+ case "match": {
943
+ const into = nodes.get(command2.into);
944
+ if (!into || command2.into === node) break;
945
+ const match = matchMotionNodes(entry.node, into.node);
946
+ const e = ease ?? MOTION_EASE_IN_OUT;
947
+ for (const [a, b] of match.pairs) push({ node: a, property: "shape", start, end, from: valueAt(a, "shape", start), to: b, ease: e, ...where });
948
+ for (const a of match.unpairedFrom) push({ node: a, property: "opacity", start, end, from: valueAt(a, "opacity", start), to: 0, ease: e, ...where });
949
+ break;
950
+ }
889
951
  case "follow":
890
952
  seg("follow", command2.to ?? 1, ease ?? MOTION_EASE_IN_OUT, start, end, { from: command2.from ?? 0, path: command2.path, rotate: command2.rotate ?? false });
891
953
  break;
@@ -1226,6 +1288,19 @@ function checkMotionScene(scene) {
1226
1288
  if (command2.type === "follow" && known(command2.path, "unknown-path", "path", where) && nodes.get(command2.path).node.type !== "path")
1227
1289
  issues.push(motionIssue("not-a-path", `"${command2.path}" is a group; follow a single path node.`, { ...where, ids: [command2.path] }));
1228
1290
  if (command2.type === "morph") known(command2.into, "unknown-shape", "shape", where);
1291
+ if (command2.type === "match" && known(command2.into, "unknown-shape", "group", where)) {
1292
+ const into = nodes.get(command2.into);
1293
+ for (const id of targets) {
1294
+ const entry = nodes.get(id);
1295
+ if (id === command2.into || into.ancestors.includes(id) || entry.ancestors.includes(command2.into)) {
1296
+ issues.push(motionIssue("match-self", `"${id}" cannot match "${command2.into}": one is the other or holds it. Match two separate groups (the second usually hidden).`, { ...where, ids: [id, command2.into] }));
1297
+ continue;
1298
+ }
1299
+ const m = matchMotionNodes(entry.node, into.node);
1300
+ if (m.unpairedInto.length)
1301
+ issues.push(motionIssue("match-unpaired", `${m.unpairedInto.map((x) => `"${x}"`).join(", ")} in "${command2.into}" ${m.unpairedInto.length > 1 ? "have" : "has"} no partner in "${id}", so ${m.unpairedInto.length > 1 ? "they do" : "it does"} not appear; add a part with the same name or id to "${id}" (it can start hidden or tiny).`, { ...where, ids: m.unpairedInto }));
1302
+ }
1303
+ }
1229
1304
  const style = command2.type === "enter" || command2.type === "exit" ? command2.style : void 0;
1230
1305
  if (style === "wipe" || style === "iris" || style === "split") {
1231
1306
  for (const id of targets)
@@ -2331,6 +2406,7 @@ var MOTION_COMMAND_SCHEMA = {
2331
2406
  stroke: { type: "string", description: "CSS colour (hex, rgb(), rgba())." },
2332
2407
  strokeWidth: { type: "number", minimum: 0, description: "Document units." }
2333
2408
  }),
2409
+ command("match", `Smart Animate: tweens a group into another group (usually hidden), part by part. Parts pair by name, then by id with the group ids taken out (card-a-title \u2194 card-b-title), then by kind in order; each takes its partner's shape, paint and place. Unpaired parts fade out; parts of "into" with no partner do not appear (check_animation lists them).`, { into: { type: "string", minLength: 1, description: "Id of the group to become." } }, ["into"]),
2334
2410
  command("morph", "Morphs each target into the shape (and paint) of another node, which usually stays hidden.", { into: { type: "string", minLength: 1, description: "Id of the node whose shape to take." } }, ["into"]),
2335
2411
  command("follow", "Moves targets along a path node, centre on the path.", {
2336
2412
  path: { type: "string", minLength: 1, description: "Id of a path node (it can be hidden)." },
@@ -2852,7 +2928,7 @@ Commands
2852
2928
  - 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.
2853
2929
  - 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.
2854
2930
  - enter also has zoom (settles from larger) and rise (rises a short way, overshooting a touch); pop and zoom take a starting "scale".
2855
- - morph turns a shape into another node's shape (keep the target hidden); follow moves along a path node (with "rotate", moves made meanwhile turn with the path).
2931
+ - 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).
2856
2932
  - 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).
2857
2933
  - 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.
2858
2934
  - 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.
@@ -2916,6 +2992,7 @@ export {
2916
2992
  interpolateMotionValue,
2917
2993
  isMotionCommand,
2918
2994
  isMotionShot,
2995
+ matchMotionNodes,
2919
2996
  motionAddTrackKey,
2920
2997
  motionClipAttrs,
2921
2998
  motionCommandDuration,