@pixerate/editor 0.223.4 → 0.223.6

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.
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -77,6 +77,16 @@ export interface DiagramEvalFixture {
77
77
  /** Tool calls that build the starting diagram (`load_diagram_mermaid`), run in order. */
78
78
  setup?: SkillEvalToolCall[];
79
79
  }
80
+ export interface RichTextEvalFixture {
81
+ editor: 'rich-text';
82
+ /** The starting document as HTML. */
83
+ html: string;
84
+ }
85
+ export interface AnimationEvalFixture {
86
+ editor: 'animation';
87
+ /** Tool calls that build the starting scene (`set_animation_artwork_svg`, `insert_shots`), run in order. */
88
+ setup?: SkillEvalToolCall[];
89
+ }
80
90
  /** One eval file of a skill. */
81
91
  export interface SkillEvalFile {
82
92
  skill: string;
@@ -20,7 +20,7 @@ export interface SkillEvalCheck extends SkillEvalToolCall {
20
20
  }
21
21
  export interface SkillEval {
22
22
  prompt: string;
23
- fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture;
23
+ fixture: SpreadsheetEvalFixture | ImageEvalFixture | VectorEvalFixture | PaintEvalFixture | VideoEvalFixture | ChartEvalFixture | DiagramEvalFixture | RichTextEvalFixture | AnimationEvalFixture;
24
24
  checks: SkillEvalCheck[];
25
25
  solution: SkillEvalToolCall[];
26
26
  }
@@ -77,6 +77,16 @@ export interface DiagramEvalFixture {
77
77
  /** Tool calls that build the starting diagram (`load_diagram_mermaid`), run in order. */
78
78
  setup?: SkillEvalToolCall[];
79
79
  }
80
+ export interface RichTextEvalFixture {
81
+ editor: 'rich-text';
82
+ /** The starting document as HTML. */
83
+ html: string;
84
+ }
85
+ export interface AnimationEvalFixture {
86
+ editor: 'animation';
87
+ /** Tool calls that build the starting scene (`set_animation_artwork_svg`, `insert_shots`), run in order. */
88
+ setup?: SkillEvalToolCall[];
89
+ }
80
90
  /** One eval file of a skill. */
81
91
  export interface SkillEvalFile {
82
92
  skill: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.223.4",
3
+ "version": "0.223.6",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
package/skills/README.md CHANGED
@@ -61,6 +61,8 @@ request like "clean up this export" finds it.
61
61
  | [`pixerate-diagram-architecture`](pixerate-diagram-architecture/SKILL.md) | Diagram (`pixerate-diagram`) | Systems at one level of detail, store shapes, tiers and trust boundaries, flows labelled with protocols. |
62
62
  | [`pixerate-rich-text-report`](pixerate-rich-text-report/SKILL.md) | Rich text (`pixerate-rich-text`) | Reports with one title and a heading hierarchy, lists, tables and charts, and a consistency pass. |
63
63
  | [`pixerate-rich-text-revise`](pixerate-rich-text-revise/SKILL.md) | Rich text (`pixerate-rich-text`) | Proofreading, renaming terms with exceptions and tightening, as minimal targeted edits. |
64
+ | [`pixerate-animation-motion-graphics`](pixerate-animation-motion-graphics/SKILL.md) | Animation (`pixerate-animation-editor`) | One idea per shot, staggered entrances, natural easing, holds to read, fitting a length, checks and export. |
65
+ | [`pixerate-animation-logo-reveal`](pixerate-animation-logo-reveal/SKILL.md) | Animation (`pixerate-animation-editor`) | A short reveal built from the logo's parts, landing on the beat and ending on the held, finished logo. |
64
66
  | [`pixerate-talking-head-edit`](pixerate-talking-head-edit/SKILL.md) | Video (`pixerate-video-editor`) | Talking-head cleanup by transcript: fillers, long pauses and stutters found in one pass and cut as one plan reviewed before it applies. Owns the dialogue tools. |
65
67
  | [`pixerate-bgm-alignment`](pixerate-bgm-alignment/SKILL.md) | Video (`pixerate-video-editor`) | Background music: beats and cuts on the beat, ducking under speech, loudness in LUFS. Owns the music tools. |
66
68
 
@@ -127,7 +129,8 @@ same change.
127
129
  "vector", "width", "height", "setup" }` with commands that draw the starting art,
128
130
  `{ "editor": "paint", "width", "height", "setup" }` `{ "editor": "video", "setup" }` or `{ "editor":
129
131
  "chart", "setup" }` `{ "editor": "diagram", "setup" }` with tool calls,
130
- or `{ "editor": "rich-text", "html" }`), `checks` (a read-only
132
+ `{ "editor": "rich-text", "html" }`, or
133
+ `{ "editor": "animation", "setup" }`), `checks` (a read-only
131
134
  tool, or `run_command` with a read-only command such as `vector.list`, its `args`, what its result must match in `expect`, and `why`) and a `solution` (the
132
135
  tool calls an expert would make). The test proves each eval: some check fails on the fixture,
133
136
  and all pass after the solution. A model harness runs the same file with the model in place
@@ -21,6 +21,14 @@ For errors, namespaced tool names and the generic command tools, see `pixerate-a
21
21
  The full command vocabulary (every type, option and default) is in
22
22
  [references/commands.md](references/commands.md).
23
23
 
24
+ ## Workflow skills
25
+
26
+ For these jobs, load the workflow skill too: it has the method, the checks between steps and
27
+ the pitfalls, and uses the tools below.
28
+
29
+ - `pixerate-animation-motion-graphics`: shots, staggered entrances, easing, holds, fitting a length.
30
+ - `pixerate-animation-logo-reveal`: a short reveal from the logo's parts, on the beat, ending held.
31
+
24
32
  ## Working method
25
33
 
26
34
  1. **Read.** `get_animation` for elements, shots with timing, clips, drivers and issues. If
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: pixerate-animation-logo-reveal
3
+ description: The logo reveal workflow in the Pixerate animation editor - a short branded intro or sting (two to four seconds) built from the logo's own parts - drawing or building the mark, bringing in the wordmark, landing beats on the music, ending on the complete logo held long enough to read, and exporting for video, web or Lottie. Use when asked to animate a logo, make a logo reveal, intro, outro, sting, bumper, brand animation or animated signature, sync a logo to music, or make a logo animation end cleanly, or when set_animation_artwork_svg, insert_shots, set_animation_settings or export_lottie are available. Load with pixerate-animation-editor, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-animation-editor
7
+ ---
8
+
9
+ # Animating a logo reveal
10
+
11
+ A logo reveal is short and has one job: the viewer remembers the logo. It builds the logo
12
+ from its own parts (the mark, then the wordmark, or the reverse), moves in a way that suits the
13
+ brand, and ends on the complete, still logo long enough to read. Everything else is
14
+ decoration and should be cut if the reveal runs long.
15
+
16
+ This skill is the method. `pixerate-animation-editor` has the full tool reference and
17
+ `references/commands.md` every command.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - Two to four seconds in total (`get_animation` `duration`), unless the brief says otherwise.
22
+ - The logo is built from its parts in a clear order: the mark first (drawn on, popped or
23
+ assembled), then the wordmark.
24
+ - It ends on the finished logo, exactly as in the brand artwork, held at least 0.5 s
25
+ (about 1 s for video).
26
+ - The motion suits the brand: calm brands ease smoothly, playful ones can spring.
27
+ - With music, the key moments land on beats.
28
+ - `check_animation` shows no errors or warnings.
29
+
30
+ ## Inputs to confirm
31
+
32
+ - **The logo artwork** (SVG with ids on the mark, the wordmark and any parts that move
33
+ separately).
34
+ - **Length, use and music:** a video intro, a web header loop, an outro; the tempo if there
35
+ is music.
36
+ - **Character:** calm, confident, playful, technical.
37
+
38
+ ## Working method
39
+
40
+ 1. **Read the artwork.** `get_animation` elements; if the logo is not loaded,
41
+ `set_animation_artwork_svg` with ids on its parts (outline text first; `get_text_layout`
42
+ shows letter units).
43
+ **Check:** every part you will animate has an id.
44
+ 2. **Plan the build** in two or three beats: the mark arrives, the wordmark follows, the logo
45
+ settles.
46
+ 3. **Write the reveal** as one or two shots with `insert_shots`: `enter` `style: "draw"` for an
47
+ outlined mark (or `pop`, `grow`, `iris`), then `enter` the wordmark (`slide`, `fade`, a
48
+ `wipe`, or `split: "letter"` for outlined text); `hold` at the end.
49
+ **Check:** `get_animation`: the mark's command starts before the wordmark's; the last shot
50
+ ends with a hold.
51
+ 4. **Hit the music.** `set_animation_settings` `beat` with the `bpm`; `get_timing_grid` lists the
52
+ beats (counted from 0 at the start: at 120 BPM beat 4 is at 2000 ms, the downbeat of bar 2);
53
+ give the wordmark's shot `at: { beat }`.
54
+ **Check:** the shot's `start` in `get_animation` is the beat's time.
55
+ 5. **Length and hold.** `fit_animation` with `seconds` and `keep` for the final hold, or
56
+ `update_shot` `hold` on the last shot.
57
+ **Check:** `duration` in range; the hold after the last command is long enough.
58
+ 6. **Check and export.** `check_animation`; `render_animation_frame` at the end (`shot`, `at:
59
+ "end"`) to see the finished logo; `export_lottie` for the web.
60
+
61
+ ## Decision table
62
+
63
+ | Mark | Entrance |
64
+ | --- | --- |
65
+ | An outline or line mark | `enter` `style: "draw"` (outline, then fill) |
66
+ | A solid, simple shape | `pop` or `grow`, `ease-out-back` |
67
+ | A mark made of parts | `enter` parts with `stagger`, or `match` into place |
68
+ | Outlined text wordmark | `enter` with `split: "letter"` |
69
+ | A wordmark beside the mark | `slide` away from the mark, or `wipe` |
70
+ | A calm brand | `fade`, `rise`, `ease-out`; no springs |
71
+ | A playful brand | `pop`, springs, a small `pulse` on landing |
72
+
73
+ ## Recipes
74
+
75
+ **Draw then word.** `insert_shots` `[{ id: "reveal", run: [{ type: "enter", targets: ["mark"],
76
+ style: "draw" }, { type: "enter", targets: ["wordmark"], style: "slide", direction: "right" }],
77
+ hold: 700 }]`.
78
+
79
+ **Hold the end.** `update_shot` `{ shot: "reveal", set: { hold: 1000 } }`.
80
+
81
+ **On beat 4 at 120 BPM.** `set_animation_settings` `{ beat: { bpm: 120 } }` → `update_shot`
82
+ `{ shot: "word", set: { at: { beat: 4 } } }`.
83
+
84
+ ## Pitfalls
85
+
86
+ - **Too long.** A reveal over four seconds loses the viewer; cut decoration first.
87
+ - **Ending in motion.** The logo must be still and complete at the end; hold it.
88
+ - **Distorting the logo** (stretching, recolouring) in the final frame; end on the brand
89
+ artwork.
90
+ - **Live text** in the artwork may render with a fallback font; outline it first.
91
+ - **Off-beat landings** with music feel wrong; cue shots to beats.
92
+ - **Too many effects** compete with the logo; one entrance per part.
93
+
94
+ ## Tools used
95
+
96
+ - `get_animation`, `get_text_layout`, `find_animation_elements`: read the logo's parts
97
+ - `set_animation_artwork_svg`: load the logo with ids
98
+ - `insert_shots`, `update_shot`, `add_commands`, `update_command`: build the reveal
99
+ - `set_animation_settings`, `get_timing_grid`: music tempo and beats
100
+ - `fit_animation`: hit the length
101
+ - `check_animation`, `render_animation_frame`: check and look at the end frame
102
+ - `export_lottie`, `export_flair_motion`: deliver
103
+ - `undo_animation`: take back a step
@@ -0,0 +1,77 @@
1
+ {
2
+ "prompt": "Make a logo reveal under three seconds: draw the circle mark on, then bring in the wordmark, and end holding the full logo for at least half a second.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 240 100\"><circle id=\"mark\" cx=\"50\" cy=\"50\" r=\"30\" fill=\"none\" stroke=\"#1d3557\" stroke-width=\"6\"/><rect id=\"wordmark\" x=\"100\" y=\"40\" width=\"120\" height=\"20\" fill=\"#1d3557\"/></svg>"
10
+ }
11
+ }
12
+ ]
13
+ },
14
+ "checks": [
15
+ {
16
+ "why": "the mark is drawn on before the wordmark arrives",
17
+ "tool": "get_animation",
18
+ "expect": {
19
+ "$expr": "(() => { const cs = v.shots.flatMap((s) => s.commands); const m = cs.find((c) => c.targets.includes('mark') && (c.type === 'draw' || (c.type === 'enter' && c.style === 'draw'))); const w = cs.find((c) => c.targets.includes('wordmark') && c.type === 'enter'); return !!m && !!w && m.start < w.start; })()"
20
+ }
21
+ },
22
+ {
23
+ "why": "under three seconds, ending on a hold of at least half a second",
24
+ "tool": "get_animation",
25
+ "expect": {
26
+ "duration": {
27
+ "$max": 3000
28
+ },
29
+ "shots": {
30
+ "$expr": "(() => { const s = v[v.length - 1]; return s.end - Math.max(...s.commands.map((c) => c.end)) >= 500; })()"
31
+ }
32
+ }
33
+ },
34
+ {
35
+ "why": "no errors or warnings in the check",
36
+ "tool": "check_animation",
37
+ "expect": {
38
+ "errors": 0,
39
+ "warnings": 0
40
+ }
41
+ }
42
+ ],
43
+ "solution": [
44
+ {
45
+ "tool": "insert_shots",
46
+ "args": {
47
+ "shots": [
48
+ {
49
+ "id": "reveal",
50
+ "label": "Logo reveal",
51
+ "run": [
52
+ {
53
+ "type": "enter",
54
+ "targets": [
55
+ "mark"
56
+ ],
57
+ "style": "draw"
58
+ },
59
+ {
60
+ "type": "enter",
61
+ "targets": [
62
+ "wordmark"
63
+ ],
64
+ "style": "slide",
65
+ "direction": "right"
66
+ }
67
+ ],
68
+ "hold": 700
69
+ }
70
+ ]
71
+ }
72
+ },
73
+ {
74
+ "tool": "check_animation"
75
+ }
76
+ ]
77
+ }
@@ -0,0 +1,76 @@
1
+ {
2
+ "prompt": "The reveal ends too abruptly. Hold the finished logo on screen for about a second at the end.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 240 100\"><circle id=\"mark\" cx=\"50\" cy=\"50\" r=\"30\" fill=\"none\" stroke=\"#1d3557\" stroke-width=\"6\"/><rect id=\"wordmark\" x=\"100\" y=\"40\" width=\"120\" height=\"20\" fill=\"#1d3557\"/></svg>"
10
+ }
11
+ },
12
+ {
13
+ "tool": "insert_shots",
14
+ "args": {
15
+ "shots": [
16
+ {
17
+ "id": "reveal",
18
+ "run": [
19
+ {
20
+ "type": "enter",
21
+ "targets": [
22
+ "mark"
23
+ ],
24
+ "style": "draw"
25
+ },
26
+ {
27
+ "type": "enter",
28
+ "targets": [
29
+ "wordmark"
30
+ ]
31
+ }
32
+ ],
33
+ "hold": 0
34
+ }
35
+ ]
36
+ }
37
+ }
38
+ ]
39
+ },
40
+ "checks": [
41
+ {
42
+ "why": "the last shot holds about a second after its last command",
43
+ "tool": "get_animation",
44
+ "expect": {
45
+ "shots": {
46
+ "$expr": "(() => { const s = v[v.length - 1]; const h = s.end - Math.max(...s.commands.map((c) => c.end)); return h >= 800 && h <= 1500; })()"
47
+ }
48
+ }
49
+ },
50
+ {
51
+ "why": "the reveal itself is unchanged",
52
+ "tool": "get_animation",
53
+ "expect": {
54
+ "shots": {
55
+ "$some": {
56
+ "id": "reveal",
57
+ "commands": {
58
+ "$length": 2
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ],
65
+ "solution": [
66
+ {
67
+ "tool": "update_shot",
68
+ "args": {
69
+ "shot": "reveal",
70
+ "set": {
71
+ "hold": 1000
72
+ }
73
+ }
74
+ }
75
+ ]
76
+ }
@@ -0,0 +1,106 @@
1
+ {
2
+ "prompt": "The sting music is 120 BPM. Make the wordmark land on beat 4: its shot should start on that beat.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 240 100\"><circle id=\"mark\" cx=\"50\" cy=\"50\" r=\"30\" fill=\"none\" stroke=\"#1d3557\" stroke-width=\"6\"/><rect id=\"wordmark\" x=\"100\" y=\"40\" width=\"120\" height=\"20\" fill=\"#1d3557\"/></svg>"
10
+ }
11
+ },
12
+ {
13
+ "tool": "insert_shots",
14
+ "args": {
15
+ "shots": [
16
+ {
17
+ "id": "mark",
18
+ "run": [
19
+ {
20
+ "type": "enter",
21
+ "targets": [
22
+ "mark"
23
+ ],
24
+ "style": "draw"
25
+ }
26
+ ],
27
+ "hold": 0
28
+ },
29
+ {
30
+ "id": "word",
31
+ "run": [
32
+ {
33
+ "type": "enter",
34
+ "targets": [
35
+ "wordmark"
36
+ ]
37
+ }
38
+ ],
39
+ "hold": 800
40
+ }
41
+ ]
42
+ }
43
+ }
44
+ ]
45
+ },
46
+ "checks": [
47
+ {
48
+ "why": "a 120 BPM beat grid",
49
+ "tool": "get_timing_grid",
50
+ "expect": {
51
+ "grid": {
52
+ "bpm": 120
53
+ }
54
+ }
55
+ },
56
+ {
57
+ "why": "the wordmark's shot starts on beat 4",
58
+ "tool": "get_animation",
59
+ "expect": {
60
+ "shots": {
61
+ "$some": {
62
+ "id": "word",
63
+ "start": {
64
+ "$in": [
65
+ 1500,
66
+ 2000
67
+ ]
68
+ }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ {
74
+ "why": "no errors or warnings in the check",
75
+ "tool": "check_animation",
76
+ "expect": {
77
+ "errors": 0,
78
+ "warnings": 0
79
+ }
80
+ }
81
+ ],
82
+ "solution": [
83
+ {
84
+ "tool": "set_animation_settings",
85
+ "args": {
86
+ "beat": {
87
+ "bpm": 120
88
+ }
89
+ }
90
+ },
91
+ {
92
+ "tool": "update_shot",
93
+ "args": {
94
+ "shot": "word",
95
+ "set": {
96
+ "at": {
97
+ "beat": 4
98
+ }
99
+ }
100
+ }
101
+ },
102
+ {
103
+ "tool": "check_animation"
104
+ }
105
+ ]
106
+ }
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: pixerate-animation-motion-graphics
3
+ description: The motion graphics workflow in the Pixerate animation editor - planning one idea per shot, staggered entrances, easing and springs that feel natural, timing and holds that let the viewer read, fitting a target length, syncing to music, checking for issues and exporting to Lottie or Flair. Use when asked to animate, add motion to, make an explainer, a title sequence, an animated infographic, a UI or icon animation, staggered or sequenced entrances, smoother or snappier motion, or to fit an animation to a length, or when insert_shots, add_commands, fit_animation or check_animation are available. Load with pixerate-animation-editor, which lists every tool.
4
+ metadata:
5
+ kind: workflow
6
+ reference: pixerate-animation-editor
7
+ ---
8
+
9
+ # Making motion graphics
10
+
11
+ Motion graphics direct attention: each shot shows one idea, things enter in the order they
12
+ should be read, motion eases the way real objects move, and the viewer gets time to read
13
+ before the next change. Write whole shots at once, let the default durations set the pace, and
14
+ change timing by shot (holds, speed, `fit_animation`) rather than nudging every command.
15
+
16
+ This skill is the method. `pixerate-animation-editor` has the full tool reference and
17
+ `references/commands.md` every command.
18
+
19
+ ## Goal and quality bar
20
+
21
+ - One idea per shot, with a `label` saying what it shows.
22
+ - Related elements enter in reading order with a short stagger (60-200 ms between them), not
23
+ all at once and not one long queue.
24
+ - Every move and entrance eases (`ease-out` for things arriving, `ease-in` for leaving,
25
+ `ease-in-out` for moves, springs for playful motion); `linear` only for continuous motion
26
+ such as a rotation or a ticker.
27
+ - Holds give the viewer time to read: about 0.5 s plus 0.3 s per short line of text.
28
+ - The length fits the brief (`fit_animation`), and `check_animation` shows no errors or
29
+ warnings.
30
+
31
+ ## Inputs to confirm
32
+
33
+ - **The message and order** the viewer should see things in.
34
+ - **Length** and **where it plays** (a loop on a site, an ad slot, a video).
35
+ - **Style:** calm and smooth, or snappy and playful; music and its tempo, if any.
36
+
37
+ ## Working method
38
+
39
+ 1. **Read.** `get_animation` for elements (ids, bounds), shots and issues. If there is no
40
+ artwork, `set_animation_artwork_svg` with an id on every element you will animate.
41
+ 2. **Plan the shots** in words: what enters, moves or changes in each, and in what order.
42
+ 3. **Write whole shots** with `insert_shots`: a `run` of commands with `targets`, `style`,
43
+ `stagger`, `ease`; leave `duration` out unless the pace must change; set `hold`.
44
+ **Check:** `get_animation`: each command's `start` and `end` are in the order you planned.
45
+ 4. **Stagger and ease.** One `enter` with several `targets` and a `stagger` for a group; `ease`
46
+ presets or springs on moves (`update_command` `set: { ease }` to fix one).
47
+ **Check:** per-target starts differ by the stagger; no move left `linear` unless meant.
48
+ 5. **Fit and cue.** `fit_animation` with `seconds` (and `keep` for shots whose pace matters,
49
+ such as a final hold); with music, `set_animation_settings` `beat` and shots `at: { beat }`
50
+ (see `pixerate-animation-logo-reveal`).
51
+ **Check:** `get_animation` `duration`.
52
+ 6. **Check.** `check_animation`: fix `too-fast`, `overlap`, `out-of-frame`, `enter-visible` and
53
+ the rest. `get_motion_path` for spacing and arcs.
54
+ 7. **Look and export.** `render_animation_frame` with `times` for a contact sheet; export with
55
+ `export_lottie` or `export_flair_motion` when asked.
56
+
57
+ ## Decision table
58
+
59
+ | Want | Command and settings |
60
+ | --- | --- |
61
+ | A group appears in reading order | `enter` with several `targets`, `stagger` 60-200 |
62
+ | Something arrives | `enter` (`fade`, `rise`, `slide`, `pop`), `ease-out` |
63
+ | Something leaves | `exit`, `ease-in`, shorter than its entrance |
64
+ | A position change | `move` with `by` or `to`, `ease-in-out` |
65
+ | Playful, bouncy | `ease` `spring-bouncy` or `ease-out-back` |
66
+ | Draw a line or outline | `draw`, or `enter` `style: "draw"` |
67
+ | Attention on one element | `pulse`, `shake`, `wiggle` (sparingly) |
68
+ | A set length | `fit_animation` with `seconds`, `keep` for fixed shots |
69
+
70
+ ## Recipes
71
+
72
+ **Staggered entrance.** `insert_shots` `[{ id: "bars", label: "Bars enter", run: [{ type:
73
+ "enter", targets: ["bar1", "bar2", "bar3"], style: "rise", stagger: 120 }], hold: 800 }]`.
74
+
75
+ **Natural move.** `update_command` `{ shot, command, set: { ease: "ease-in-out" } }`.
76
+
77
+ **Exactly four seconds.** `fit_animation` `{ seconds: 4 }`; then `check_animation` for
78
+ `too-fast` if shots were compressed a lot.
79
+
80
+ ## Pitfalls
81
+
82
+ - **Everything at once.** Simultaneous entrances give the eye nowhere to start.
83
+ - **Linear moves** look mechanical; ease them.
84
+ - **Hand-set durations everywhere** make pacing inconsistent; keep the defaults and change pace
85
+ per shot.
86
+ - **No hold** at the end of a shot: the viewer cannot read it before it changes.
87
+ - **Overlapping commands** on one element fight each other (`overlap` in `check_animation`).
88
+ - **Elements without ids** in the artwork cannot be animated; add ids before importing.
89
+
90
+ ## Tools used
91
+
92
+ - `get_animation`, `get_shot`, `find_animation_elements`, `describe_animation_command`: read scenes, elements and command fields
93
+ - `set_animation_artwork_svg`: the artwork with ids
94
+ - `insert_shots`, `update_shot`, `delete_shots`, `move_shot`: whole shots
95
+ - `add_commands`, `update_command`, `delete_commands`, `move_command`: refine commands
96
+ - `edit_animation`: several edits as one undo step
97
+ - `fit_animation`, `set_animation_settings`, `get_timing_grid`: length, settings and cues
98
+ - `check_animation`, `get_motion_path`, `render_animation_frame`: check and look
99
+ - `export_lottie`, `export_flair_motion`: deliver
100
+ - `undo_animation`: take back a step
@@ -0,0 +1,109 @@
1
+ {
2
+ "prompt": "This animation needs to be exactly 4 seconds long for the ad slot. Keep the pacing natural.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 200 100\"><rect id=\"bar1\" x=\"20\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar2\" x=\"60\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar3\" x=\"100\" y=\"20\" width=\"20\" height=\"60\"/></svg>"
10
+ }
11
+ },
12
+ {
13
+ "tool": "insert_shots",
14
+ "args": {
15
+ "shots": [
16
+ {
17
+ "id": "intro",
18
+ "run": [
19
+ {
20
+ "type": "enter",
21
+ "targets": [
22
+ "bar1",
23
+ "bar2",
24
+ "bar3"
25
+ ],
26
+ "stagger": 200
27
+ }
28
+ ],
29
+ "hold": 600
30
+ },
31
+ {
32
+ "id": "middle",
33
+ "run": [
34
+ {
35
+ "type": "move",
36
+ "targets": [
37
+ "bar2"
38
+ ],
39
+ "by": {
40
+ "x": 0,
41
+ "y": -20
42
+ }
43
+ }
44
+ ],
45
+ "hold": 1500
46
+ },
47
+ {
48
+ "id": "end",
49
+ "run": [
50
+ {
51
+ "type": "exit",
52
+ "targets": [
53
+ "bar1",
54
+ "bar2",
55
+ "bar3"
56
+ ]
57
+ }
58
+ ],
59
+ "hold": 1000
60
+ }
61
+ ]
62
+ }
63
+ }
64
+ ]
65
+ },
66
+ "checks": [
67
+ {
68
+ "why": "exactly 4 seconds",
69
+ "tool": "get_animation",
70
+ "expect": {
71
+ "duration": {
72
+ "$min": 3980,
73
+ "$max": 4020
74
+ }
75
+ }
76
+ },
77
+ {
78
+ "why": "the same shots, in order",
79
+ "tool": "get_animation",
80
+ "expect": {
81
+ "shots": {
82
+ "$expr": "v.map((s) => s.id).join() === 'intro,middle,end'"
83
+ }
84
+ }
85
+ },
86
+ {
87
+ "why": "no errors or warnings in the check",
88
+ "tool": "check_animation",
89
+ "expect": {
90
+ "errors": 0,
91
+ "warnings": 0
92
+ }
93
+ }
94
+ ],
95
+ "solution": [
96
+ {
97
+ "tool": "get_animation"
98
+ },
99
+ {
100
+ "tool": "fit_animation",
101
+ "args": {
102
+ "seconds": 4
103
+ }
104
+ },
105
+ {
106
+ "tool": "check_animation"
107
+ }
108
+ ]
109
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "prompt": "The bar's slide across looks mechanical. Make the movement feel natural.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 200 100\"><rect id=\"bar1\" x=\"20\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar2\" x=\"60\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar3\" x=\"100\" y=\"20\" width=\"20\" height=\"60\"/></svg>"
10
+ }
11
+ },
12
+ {
13
+ "tool": "insert_shots",
14
+ "args": {
15
+ "shots": [
16
+ {
17
+ "id": "slide",
18
+ "run": [
19
+ {
20
+ "type": "move",
21
+ "targets": [
22
+ "bar1"
23
+ ],
24
+ "by": {
25
+ "x": 60,
26
+ "y": 0
27
+ },
28
+ "ease": "linear"
29
+ }
30
+ ]
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ ]
36
+ },
37
+ "checks": [
38
+ {
39
+ "why": "the move eases instead of running at constant speed",
40
+ "tool": "get_animation",
41
+ "expect": {
42
+ "shots": {
43
+ "$some": {
44
+ "id": "slide",
45
+ "commands": {
46
+ "$some": {
47
+ "type": "move",
48
+ "$expr": "v.ease !== 'linear'"
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ },
55
+ {
56
+ "why": "it still slides the same distance",
57
+ "tool": "get_animation",
58
+ "expect": {
59
+ "shots": {
60
+ "$some": {
61
+ "id": "slide",
62
+ "commands": {
63
+ "$some": {
64
+ "type": "move",
65
+ "by": {
66
+ "x": 60,
67
+ "y": 0
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ },
75
+ {
76
+ "why": "no errors or warnings in the check",
77
+ "tool": "check_animation",
78
+ "expect": {
79
+ "errors": 0,
80
+ "warnings": 0
81
+ }
82
+ }
83
+ ],
84
+ "solution": [
85
+ {
86
+ "tool": "update_command",
87
+ "args": {
88
+ "shot": "slide",
89
+ "command": 0,
90
+ "set": {
91
+ "ease": "ease-in-out"
92
+ }
93
+ }
94
+ }
95
+ ]
96
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "prompt": "Animate the three bars coming in one after another with a short, snappy stagger, then hold on all three.",
3
+ "fixture": {
4
+ "editor": "animation",
5
+ "setup": [
6
+ {
7
+ "tool": "set_animation_artwork_svg",
8
+ "args": {
9
+ "svg": "<svg viewBox=\"0 0 200 100\"><rect id=\"bar1\" x=\"20\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar2\" x=\"60\" y=\"20\" width=\"20\" height=\"60\"/><rect id=\"bar3\" x=\"100\" y=\"20\" width=\"20\" height=\"60\"/></svg>"
10
+ }
11
+ }
12
+ ]
13
+ },
14
+ "checks": [
15
+ {
16
+ "why": "each bar enters, at a different time, within about half a second of each other",
17
+ "tool": "get_animation",
18
+ "expect": {
19
+ "$expr": "(() => { const starts = {}; for (const s of v.shots) for (const c of s.commands) if (c.type === 'enter') c.targets.forEach((t, i) => { starts[t] = c.start + (c.delay || 0) + i * (c.stagger || 0); }); const xs = ['bar1', 'bar2', 'bar3'].map((b) => starts[b]); return xs.every((x) => x !== undefined) && new Set(xs).size === 3 && Math.max(...xs) - Math.min(...xs) <= 600; })()"
20
+ }
21
+ },
22
+ {
23
+ "why": "it ends holding on the bars",
24
+ "tool": "get_animation",
25
+ "expect": {
26
+ "shots": {
27
+ "$expr": "(() => { const s = v[v.length - 1]; return s.end - Math.max(...s.commands.map((c) => c.end)) >= 300; })()"
28
+ }
29
+ }
30
+ },
31
+ {
32
+ "why": "no errors or warnings in the check",
33
+ "tool": "check_animation",
34
+ "expect": {
35
+ "errors": 0,
36
+ "warnings": 0
37
+ }
38
+ }
39
+ ],
40
+ "solution": [
41
+ {
42
+ "tool": "insert_shots",
43
+ "args": {
44
+ "shots": [
45
+ {
46
+ "id": "bars",
47
+ "label": "Bars enter",
48
+ "run": [
49
+ {
50
+ "type": "enter",
51
+ "targets": [
52
+ "bar1",
53
+ "bar2",
54
+ "bar3"
55
+ ],
56
+ "style": "rise",
57
+ "stagger": 120
58
+ }
59
+ ],
60
+ "hold": 800
61
+ }
62
+ ]
63
+ }
64
+ },
65
+ {
66
+ "tool": "check_animation"
67
+ }
68
+ ]
69
+ }