@nodetool-ai/agents 0.8.4-a24.3 → 0.8.5

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.
Files changed (156) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
  3. package/dist/_skills/README.md +211 -0
  4. package/dist/_skills/api-agents/SKILL.md +58 -0
  5. package/dist/_skills/api-apps/SKILL.md +54 -0
  6. package/dist/_skills/api-assets/SKILL.md +66 -0
  7. package/dist/_skills/api-collections/SKILL.md +33 -0
  8. package/dist/_skills/api-games/SKILL.md +81 -0
  9. package/dist/_skills/api-media/SKILL.md +131 -0
  10. package/dist/_skills/api-memory/SKILL.md +72 -0
  11. package/dist/_skills/api-models/SKILL.md +78 -0
  12. package/dist/_skills/api-scripts/SKILL.md +79 -0
  13. package/dist/_skills/api-settings/SKILL.md +49 -0
  14. package/dist/_skills/api-sketches/SKILL.md +61 -0
  15. package/dist/_skills/api-storyboards/SKILL.md +94 -0
  16. package/dist/_skills/api-timelines/SKILL.md +151 -0
  17. package/dist/_skills/api-web/SKILL.md +57 -0
  18. package/dist/_skills/api-workflows/SKILL.md +123 -0
  19. package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
  20. package/dist/_skills/caption-titles/SKILL.md +94 -0
  21. package/dist/_skills/color-motion/SKILL.md +185 -0
  22. package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
  23. package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
  24. package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
  25. package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
  26. package/dist/_skills/frame-composition/SKILL.md +291 -0
  27. package/dist/_skills/game-direction/SKILL.md +99 -0
  28. package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
  29. package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
  30. package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
  31. package/dist/_skills/launch-commercial/SKILL.md +267 -0
  32. package/dist/_skills/launch-kit/SKILL.md +120 -0
  33. package/dist/_skills/logo-reveal/SKILL.md +225 -0
  34. package/dist/_skills/manimgx/SKILL.md +123 -0
  35. package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
  36. package/dist/_skills/motion-ad/SKILL.md +281 -0
  37. package/dist/_skills/motion-background/SKILL.md +145 -0
  38. package/dist/_skills/motion-curves/SKILL.md +302 -0
  39. package/dist/_skills/motion-direction/SKILL.md +259 -0
  40. package/dist/_skills/motion-graphics/SKILL.md +217 -0
  41. package/dist/_skills/motion-principles/SKILL.md +255 -0
  42. package/dist/_skills/music-video-treatment/SKILL.md +40 -0
  43. package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
  44. package/dist/_skills/native-game/SKILL.md +310 -0
  45. package/dist/_skills/native-game-reference/SKILL.md +879 -0
  46. package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
  47. package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
  48. package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
  49. package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
  50. package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
  51. package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
  52. package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
  53. package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
  54. package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
  55. package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
  56. package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
  57. package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
  58. package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
  59. package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
  60. package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
  61. package/dist/_skills/product-commercial/SKILL.md +132 -0
  62. package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
  63. package/dist/_skills/script-video/SKILL.md +106 -0
  64. package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
  65. package/dist/_skills/seedream-prompting/SKILL.md +117 -0
  66. package/dist/_skills/short-film/SKILL.md +123 -0
  67. package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
  68. package/dist/_skills/storyboard-core/SKILL.md +716 -0
  69. package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
  70. package/dist/_skills/trailer-template/SKILL.md +291 -0
  71. package/dist/_skills/ugc-video/SKILL.md +186 -0
  72. package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
  73. package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
  74. package/dist/_skills/video-clone/SKILL.md +96 -0
  75. package/dist/_skills/video-workflow/SKILL.md +119 -0
  76. package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
  77. package/dist/analysis/audio-dsp.d.ts +1 -60
  78. package/dist/analysis/audio-dsp.d.ts.map +1 -1
  79. package/dist/analysis/audio-dsp.js +3 -190
  80. package/dist/analysis/audio-dsp.js.map +1 -1
  81. package/dist/app-build/bridge.d.ts.map +1 -1
  82. package/dist/app-build/bridge.js +44 -17
  83. package/dist/app-build/bridge.js.map +1 -1
  84. package/dist/capabilities/analysis.d.ts.map +1 -1
  85. package/dist/capabilities/analysis.js +9 -35
  86. package/dist/capabilities/analysis.js.map +1 -1
  87. package/dist/capabilities/apps.d.ts.map +1 -1
  88. package/dist/capabilities/apps.js +55 -13
  89. package/dist/capabilities/apps.js.map +1 -1
  90. package/dist/capabilities/apps.specs.d.ts.map +1 -1
  91. package/dist/capabilities/apps.specs.js +5 -3
  92. package/dist/capabilities/apps.specs.js.map +1 -1
  93. package/dist/capabilities/assets.d.ts.map +1 -1
  94. package/dist/capabilities/assets.js +10 -6
  95. package/dist/capabilities/assets.js.map +1 -1
  96. package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
  97. package/dist/capabilities/game-asset-browser.js +6 -1
  98. package/dist/capabilities/game-asset-browser.js.map +1 -1
  99. package/dist/capabilities/game.d.ts +1 -1
  100. package/dist/capabilities/game.d.ts.map +1 -1
  101. package/dist/capabilities/game.js +71 -56
  102. package/dist/capabilities/game.js.map +1 -1
  103. package/dist/capabilities/game.specs.js +1 -1
  104. package/dist/capabilities/game.specs.js.map +1 -1
  105. package/dist/capabilities/game3d.d.ts.map +1 -1
  106. package/dist/capabilities/game3d.js +14 -19
  107. package/dist/capabilities/game3d.js.map +1 -1
  108. package/dist/capabilities/gameAssertions.d.ts +52 -0
  109. package/dist/capabilities/gameAssertions.d.ts.map +1 -0
  110. package/dist/capabilities/gameAssertions.js +32 -0
  111. package/dist/capabilities/gameAssertions.js.map +1 -0
  112. package/dist/capabilities/models.d.ts.map +1 -1
  113. package/dist/capabilities/models.js +9 -0
  114. package/dist/capabilities/models.js.map +1 -1
  115. package/dist/capabilities/project-scope.d.ts +8 -0
  116. package/dist/capabilities/project-scope.d.ts.map +1 -1
  117. package/dist/capabilities/project-scope.js +16 -0
  118. package/dist/capabilities/project-scope.js.map +1 -1
  119. package/dist/capabilities/storyboards.d.ts.map +1 -1
  120. package/dist/capabilities/storyboards.js +36 -11
  121. package/dist/capabilities/storyboards.js.map +1 -1
  122. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  123. package/dist/capabilities/storyboards.specs.js +11 -1
  124. package/dist/capabilities/storyboards.specs.js.map +1 -1
  125. package/dist/capabilities/workflows.d.ts.map +1 -1
  126. package/dist/capabilities/workflows.js +1 -0
  127. package/dist/capabilities/workflows.js.map +1 -1
  128. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  129. package/dist/capabilities/workflows.specs.js +4 -3
  130. package/dist/capabilities/workflows.specs.js.map +1 -1
  131. package/dist/codeact/nodetool-api.d.ts.map +1 -1
  132. package/dist/codeact/nodetool-api.js +8 -2
  133. package/dist/codeact/nodetool-api.js.map +1 -1
  134. package/dist/evals/surfaces/game.d.ts.map +1 -1
  135. package/dist/evals/surfaces/game.js +29 -1
  136. package/dist/evals/surfaces/game.js.map +1 -1
  137. package/dist/game-authoring.d.ts +120 -0
  138. package/dist/game-authoring.d.ts.map +1 -1
  139. package/dist/game-build-integrity.d.ts +12 -0
  140. package/dist/game-build-integrity.d.ts.map +1 -0
  141. package/dist/game-build-integrity.js +78 -0
  142. package/dist/game-build-integrity.js.map +1 -0
  143. package/dist/game-examples.d.ts +44 -3
  144. package/dist/game-examples.d.ts.map +1 -1
  145. package/dist/game-examples.js +60 -10
  146. package/dist/game-examples.js.map +1 -1
  147. package/dist/system-skills.d.ts +1 -1
  148. package/dist/system-skills.js +1 -1
  149. package/dist/tools/builtin-tools.d.ts.map +1 -1
  150. package/dist/tools/builtin-tools.js +2 -0
  151. package/dist/tools/builtin-tools.js.map +1 -1
  152. package/dist/tools/external-mcp-tools.d.ts +6 -0
  153. package/dist/tools/external-mcp-tools.d.ts.map +1 -1
  154. package/dist/tools/external-mcp-tools.js +15 -4
  155. package/dist/tools/external-mcp-tools.js.map +1 -1
  156. package/package.json +19 -19
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: motion-background
3
+ description: Build an ambient backdrop on a NodeTool timeline using shape gradients, procedural generator effects, slow loops, or generated video. Use for title cards, hero beds, end cards, lower-third plates, and quiet loops behind a subject.
4
+ ---
5
+
6
+ # Motion Background → the bed
7
+
8
+ A background that draws attention has failed. Slow, low contrast, looping with
9
+ no visible join, and cheap enough that the picture in front of it still
10
+ composites.
11
+
12
+ `timeline-edit-ops` carries the op contract for the calls named here.
13
+ `color-motion` chooses the palette the bed is built from, and `motion-curves`
14
+ writes the loop when no wrapping preset holds the seam.
15
+
16
+ ## Pick the cheapest thing that reads
17
+
18
+ | Look | Build it from | Cost |
19
+ |---|---|---|
20
+ | Flat or gradient wash | One full-frame `rect` shape with `fillStyle` linear or radial | Shape clips do not count against the video layer cap |
21
+ | Soft mesh or conic wash | One full-frame shape with a `generator` effect, `mode: "meshGradient"` or `"conicGradient"` | One procedural effect, no model call |
22
+ | Shaped colour blobs | Radial-filled `ellipse` clips, low opacity, blurred | More control over each blob's motion |
23
+ | Noise, fractal, particles, light leak, grid | One full-frame shape with the matching `generator` mode | Procedural and seedable |
24
+ | Drifting texture | An image clip with a `kenBurns` animation | Outside the video layer cap |
25
+ | Smoke, ink, or clouds | A generated video bed, looped | One video layer and a model call |
26
+ | Colour cycle | Any of the above plus a `hueShift` loop | An additional grade |
27
+
28
+ At most **eight video layers** composite at once, resolved from the lowest track
29
+ index. Text, shape and image clips are not counted, so a bed built from shapes
30
+ costs nothing in that budget. Nine overlapping video clips means the bottom one
31
+ silently does not draw, reported as `layer_cap_exceeded`.
32
+
33
+ ## A procedural bed
34
+
35
+ Add a full-frame shape and apply a generator effect to it. Modes are `noise`,
36
+ `fractal`, `conicGradient`, `meshGradient`, `gradientField`, `particles`,
37
+ `lightLeak`, and `gridPattern`. `colorA` and `colorB` set the two colours;
38
+ `scale`, `amount`, `angle`, `time`, and `seed` shape the field. For movement,
39
+ set `animate: true`, which samples the current timeline time into the effect.
40
+ Keep a fixed `seed` for repeatable frames. This clock keeps moving; it does not
41
+ make the procedural field wrap at the clip boundary. For a seamless loop, use
42
+ a wrapping clip animation or match the endpoints in a rendered loop.
43
+
44
+ ```json
45
+ {"op": "set_effects", "target": "Bed", "effects": [
46
+ {"type": "generator", "mode": "meshGradient",
47
+ "colorA": "#10233c", "colorB": "#ec6b45", "scale": 6,
48
+ "amount": 0.6, "seed": 9, "animate": true}]}
49
+ ```
50
+
51
+ For a dark wash that bands, put a fixed-seed `stylize` `dither` after the
52
+ generator or gradient in the effect list. Dither breaks up 8-bit steps but
53
+ cannot restore precision lost in the source. `color-motion` covers this look.
54
+
55
+ ## A mesh from shapes
56
+
57
+ For individual moving blobs, stack radial fills over a base. Each ellipse is
58
+ its own clip on an overlay track, at 30–60% opacity, with a `blur` effect wide
59
+ enough that no edge reads:
60
+
61
+ ```json
62
+ {"op": "add_shape_clip", "shape": {"kind": "ellipse",
63
+ "x": 0.05, "y": 0.1, "width": 0.6, "height": 0.7,
64
+ "fillStyle": {"type": "radial", "stops": [
65
+ {"offset": 0, "color": "#5b8cff"}, {"offset": 1, "color": "#5b8cff00"}]}},
66
+ "durationMs": 12000}
67
+ ```
68
+
69
+ ```json
70
+ {"op": "set_effects", "target": "Ellipse", "effects": [{"type": "blur", "radius": 120}]}
71
+ ```
72
+
73
+ Then give each blob a different `loop`: `float` with `amplitude` 0.01–0.015
74
+ (a fraction of frame height each way, so at most 3% of travel),
75
+ `breathe` at `intensity` 0.03–0.08, and cycle lengths that do not divide each
76
+ other — 9000, 13000, 17000ms — so the composite never visibly repeats.
77
+
78
+ `blendMode` `screen` or `add` on a blob over a dark base builds light rather than
79
+ covering it; `multiply` or `soft-light` deepens instead. That choice matters more
80
+ than the fill colours.
81
+
82
+ ## Loops that actually loop
83
+
84
+ `float`, `breathe`, `rotate` and `hueShift` are built to wrap: they start and end
85
+ at the same value, so `durationMs` is a period and the seam is invisible. A
86
+ custom loop curve must do the same — the value at `t: 1` has to equal the value
87
+ at `t: 0` or the bed jumps every cycle.
88
+
89
+ `kenBurns` is the exception: it is a one-shot across the whole clip, holding at
90
+ its end, so it does not repeat and its `durationMs` is ignored. On a long bed a
91
+ drift from `kenBurns` finishes and then sits still. For continuous drift over a
92
+ long clip use `float` with a long period instead.
93
+
94
+ Periods that read as ambient: 8000–30000ms. Anything under about 5000ms reads as
95
+ a screensaver, and text in front of it becomes hard to hold.
96
+
97
+ ## Keep it behind
98
+
99
+ - Background sits on the **highest** track index, because the lowest draws on
100
+ top. A bed that covers the picture is a track-order mistake, not an opacity
101
+ one.
102
+ - Contrast: pull it down with a `color` effect at `saturation` 0.5–0.7 and
103
+ `brightness` slightly negative before touching opacity. A desaturated bed
104
+ reads as depth; a faded one reads as a mistake.
105
+ - Amplitude: nothing in the bed should travel more than about 3% of the frame,
106
+ or the eye starts tracking it.
107
+ - Give the foreground a scrim if the bed's bright regions cross the type: a black
108
+ `rect` shape at 40–60% opacity on a track index between the bed and the text.
109
+ `validate_timeline` reports `text_illegible` for what it can measure, and says
110
+ nothing about gradients — look at the frame.
111
+
112
+ ## A generated bed
113
+
114
+ For smoke, ink, or clouds that the procedural modes cannot draw, generate video.
115
+ Use `find_model` with `text_to_video`, then `generate_video`,
116
+ then `add_media_clip` with the returned `asset://` reference. The `find_model`
117
+ result names a `prompting_skill` for the line it picked; load it, because each
118
+ line wants the prompt shaped differently. Whatever the line, prompt for slow,
119
+ even motion with no cuts and no subject; a bed with an event in it is a shot,
120
+ not a bed. On a line that writes native audio (Seedance 2, Veo 3, MiniMax H3,
121
+ Kling 2.6 and later) the bed arrives with a soundtrack: either brief it as room
122
+ tone and keep it, or mute the clip under the track you meant to use —
123
+ `video-audio-continuity` is the rule for which.
124
+
125
+ A generated clip does not loop. Cover a longer sequence by duplicating it and
126
+ crossfading the joins: overlap the copies by 800–1500ms on one track, which
127
+ dissolves with no call at all, or `set_transition` `crossfade` on the incoming
128
+ copy to pin the length.
129
+
130
+ ## Reduced motion, and knowing when to stop
131
+
132
+ A bed exists to sit under something. Before adding a second idea to it, check
133
+ whether the first one is doing anything at all — a drift too slow to see is
134
+ budget spent on nothing, and one too fast is the reason nobody read the headline.
135
+ One idea, one amplitude, one period family.
136
+
137
+ ## Check it
138
+
139
+ - `preview_timeline_frame` with a `range` across a full cycle and `sheet: true`,
140
+ which is how you see whether a loop breathes evenly or lurches. Sampling three
141
+ timecodes will not show a seam.
142
+ - The same sweep with the foreground on, to confirm the type still reads at every
143
+ point in the cycle rather than only where you happened to look.
144
+ - `render_timeline` at a low `preview_scale` while iterating. A bed is judged
145
+ over seconds, and a still cannot tell you whether it is calm.
@@ -0,0 +1,302 @@
1
+ ---
2
+ name: motion-curves
3
+ description: Write NodeTool timeline animation curves by hand or bake a JavaScript body into curves once. Use for overshoot, bounce, anticipation, arcs, path draw-on, style-property animation, or motion a preset cannot express.
4
+ ---
5
+
6
+ # Motion Curves → animation nobody had a preset for
7
+
8
+ An `animate_clip` input with `{"preset":"custom"}` carries exactly one of
9
+ `curves` (keyframes you write) or `code` (a JS body baked into curves once,
10
+ host-side). Add `mask` when a curve drives `wipeProgress`. A stored document
11
+ animation can instead carry `styleTracks` or `textAnimator` without numeric
12
+ custom curves.
13
+
14
+ `timeline-edit-ops` carries the op contract and the preset catalog this file is
15
+ the escape hatch from. `motion-principles` gives the duration and the shape a
16
+ curve should hit before you write its keyframes.
17
+
18
+ For numeric custom curves, call `edit_timeline` with an `animate_clip` op or
19
+ the in-product `ui_timeline_animate_clip` tool. Both accept the flat `curves`
20
+ or `code` input shown below. For stored `styleTracks`, `textAnimator`, or
21
+ `beat`, read `get_timeline`, edit the full clip animation in its document,
22
+ then call `set_timeline_document` with every existing document field. The
23
+ `animate_clip` input does not author those stored fields.
24
+
25
+ ## When to write one
26
+
27
+ Write curves as soon as a preset is close but not right. The catalog is eight
28
+ entrance shapes and a handful of beats — where motion starts, not where it
29
+ stops — and a piece whose every title arrives with `pop` reads like a template.
30
+ A curve costs one field and a few numbers, and the recipes below are the shapes
31
+ worth reaching for: an entrance that overshoots twice, a hold in the middle of a
32
+ move, two channels on different schedules, a decay, an arc, a wiggle, a path
33
+ draw-on.
34
+
35
+ Two shapes need no curve, and writing one anyway only makes the document harder
36
+ to read: a single overshoot and a physical settle are already `easing` strings.
37
+ `cubic-bezier(0.16,1,0.3,1)` and `spring(180,12,1)` on an ordinary preset do
38
+ both. Everything past that is this file.
39
+
40
+ ## Curves by hand
41
+
42
+ ```json
43
+ {"op": "animate_clip", "target": "Title", "animations": [
44
+ {"role": "in", "preset": "custom", "durationMs": 600,
45
+ "curves": [
46
+ {"property": "offsetY", "keyframes": [
47
+ {"t": 0, "value": 80}, {"t": 0.7, "value": -12, "easing": "easeOut"},
48
+ {"t": 1, "value": 0, "easing": "easeInOut"}]},
49
+ {"property": "opacity", "keyframes": [
50
+ {"t": 0, "value": 0}, {"t": 0.4, "value": 1}]}]}]}
51
+ ```
52
+
53
+ `t` runs 0..1 over the animation's **window**, not the clip. `easing` on a
54
+ keyframe eases the segment **ending** there. Keyframes are sorted, `t` is clamped
55
+ to 0..1, and a curve that stops short of either end is extended by holding its
56
+ end value — so sampling 0.05..0.95 does what it looks like.
57
+
58
+ Limits: 16 curves per animation, one per property, 4096 keyframes each.
59
+
60
+ `durationMs` defaults to the clip's own duration, so curves span the whole clip
61
+ unless you say otherwise.
62
+
63
+ ## The channels, and how they combine
64
+
65
+ Several animations can drive one channel. How they fold decides whether the
66
+ second adds to the first or throws it away.
67
+
68
+ | Fold | Channels | Identity | Result |
69
+ |---|---|---|---|
70
+ | add | `offsetX`, `offsetY`, `rotation` (radians), `rotationX`, `rotationY` (deg), `blur` (px), `brightness` (−1..1), `hue` (deg), `temperature`, `tint`, `cameraAzimuth`, `cameraElevation`, `cameraFov` (deg, 3D clips only) | 0 | Values sum |
71
+ | multiply | `scale`, `scaleX`, `scaleY`, `opacity` (0..1), `saturation` (0..4), `contrast` (0..4), `cameraZoom` (3D clips only) | 1 | Values multiply |
72
+ | min | `wipeProgress` (0..1) | 1 | The tightest wipe wins |
73
+ | replace | `positionX`, `positionY` (canvas px), `anchorX`, `anchorY` (0..1), `trimStart`, `trimEnd` (0..1) | none | The last animation in document order wins; the other is discarded |
74
+
75
+ The replace row is the trap. Two animations driving `positionX` over the same
76
+ instants means one does nothing, reported as `replace_curves_overlap`. Move a
77
+ clip with `offsetX` when it should compose with other motion, and with
78
+ `positionX` only for an absolute placement nothing else touches.
79
+
80
+ Land the last keyframe on the identity value unless you mean the clip to stay
81
+ where the curve stopped — and remember the role decides whether it stays: an
82
+ `in` window holds its `t: 0` values before it and contributes nothing after, an
83
+ `out` window holds its `t: 1` values after it, `emphasis` holds neither, and
84
+ `loop` repeats its cycle, so a loop curve must end on the value it started on.
85
+
86
+ `list_animation_presets` reports this table from the engine. Read it there rather
87
+ than from memory when a channel's range matters.
88
+
89
+ ## Style curves in a document
90
+
91
+ Use `styleTracks` on a stored animation when the target is a visual property
92
+ rather than a numeric motion channel. The target names a clip style such as
93
+ `text.color`, `shape.cornerRadius`, `mask.featherPx`, or
94
+ `clip.borderRadius`; an effect target is
95
+ `effect.<effect-id>.<numeric-field>` or a supported effect color such as
96
+ `effect.bg-field.colorB`. A target needs the matching style,
97
+ mask, or effect on the clip. Color values interpolate through the supported
98
+ CSS color parser. `shape.d` and `mask.d` paths need matching commands and point
99
+ counts. Glyph targets need a text stagger. `validate_timeline` reports an
100
+ invalid target, incompatible path, or unsupported color.
101
+
102
+ This is one complete stored animation object to merge into an existing clip's
103
+ `animations` array before the full-document write:
104
+
105
+ ```json
106
+ {"id":"round-in","role":"in","preset":"fade","durationMs":600,"styleTracks":[{"target":"clip.borderRadius","keyframes":[{"t":0,"value":0},{"t":1,"value":30,"easing":"easeOutExpo"}]}]}
107
+ ```
108
+
109
+ `styleTracks` can accompany a preset, so a title can fade while its color
110
+ changes. The `id` is required in the stored document; `animate_clip` creates
111
+ one for its own inputs. Use `timeline-edit-ops` § Document motion fields for
112
+ links, text animators, beat binding, and the document write boundary.
113
+
114
+ ## The code body
115
+
116
+ A JavaScript body run **once**, at author time, in the QuickJS sandbox. It
117
+ returns keyframes; the keyframes are stored on the clip; every renderer samples
118
+ those. Nothing evaluates per frame, which is why a body cannot react to playback
119
+ and why the same animation looks identical in the preview, the export and the
120
+ headless compositor.
121
+
122
+ The bake is hermetic: no toolbelt, no `nodetool.*`, no network, no secrets. Just
123
+ arithmetic, a 10-second ceiling, and `inputs`:
124
+
125
+ | `inputs` field | What it is |
126
+ |---|---|
127
+ | `role` | `in`, `out`, `emphasis` or `loop` |
128
+ | `durationMs` | The animation's own window |
129
+ | `clipDurationMs` | The clip it sits on |
130
+ | `canvasWidth`, `canvasHeight` | Sequence pixels — turn a normalized distance into px here |
131
+ | `params` | The animation's `params`, untouched |
132
+ | `staggerCount` | Units a text clip splits into, or 0 |
133
+ | `sampleCount` | Suggested density for a continuous function |
134
+
135
+ Return `{curves}` — the same shape as the JSON above — or `{samples}`, one bag
136
+ per point in time:
137
+
138
+ ```js
139
+ const n = inputs.sampleCount;
140
+ const samples = [];
141
+ for (let i = 0; i < n; i++) {
142
+ const t = i / (n - 1);
143
+ samples.push({ t, offsetY: 80 * (1 - t), opacity: t });
144
+ }
145
+ return { samples };
146
+ ```
147
+
148
+ Returning both is an error, as is returning neither. A property present on some
149
+ samples and absent from others is an error rather than a hole — interpolating
150
+ across the gap would invent motion nobody wrote. A sample needs a finite numeric
151
+ `t`; `easing` on a sample is optional and applies to the segment ending there.
152
+ At most 4096 samples.
153
+
154
+ Because the bake runs once, a body using `Math.random` is frozen at the value it
155
+ drew: the result is stable forever after, but re-baking gives different motion.
156
+ Seed your own generator when the same body has to produce the same wiggle twice.
157
+
158
+ ## Recipes
159
+
160
+ **Overshoot and settle.** Two overshoots, the second smaller — what a single
161
+ `easeOutBack` cannot express.
162
+
163
+ ```js
164
+ const p = [[0, 0.6], [0.45, 1.09], [0.68, 0.97], [0.85, 1.02], [1, 1]];
165
+ return { curves: [{ property: "scale",
166
+ keyframes: p.map(([t, value]) => ({ t, value, easing: "easeInOut" })) }] };
167
+ ```
168
+
169
+ **Decaying bounce.** Hops that die out and land exactly at rest.
170
+
171
+ ```js
172
+ const n = inputs.sampleCount, h = 0.06 * inputs.canvasHeight, hops = 3;
173
+ const samples = [];
174
+ for (let i = 0; i < n; i++) {
175
+ const t = i / (n - 1);
176
+ samples.push({ t, offsetY: -h * Math.abs(Math.sin(Math.PI * hops * t)) * (1 - t) });
177
+ }
178
+ return { samples };
179
+ ```
180
+
181
+ **Anticipation.** A counter-move before the action, 60–120ms of the window.
182
+
183
+ ```js
184
+ return { curves: [{ property: "offsetY", keyframes: [
185
+ { t: 0, value: 0 }, { t: 0.18, value: 10, easing: "easeInOut" },
186
+ { t: 0.8, value: -4, easing: "easeOut" }, { t: 1, value: 0 }] }] };
187
+ ```
188
+
189
+ **Shaped wiggle.** For a steady seeded wiggle, use a stored
190
+ `animationLinks` entry with `kind: "wiggle"` as described in
191
+ `timeline-edit-ops` § Document motion fields. Bake a curve when its amplitude must fade in or out. This
192
+ seeded generator reproduces the same shape on a re-bake.
193
+
194
+ ```js
195
+ const n = inputs.sampleCount;
196
+ const amp = Number(inputs.params.amplitude ?? 6);
197
+ let seed = Number(inputs.params.seed ?? 1);
198
+ const rand = () => (seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648 - 0.5;
199
+ const samples = [];
200
+ for (let i = 0; i < n; i++) {
201
+ const t = i / (n - 1);
202
+ const fade = Math.min(1, 4 * t, 4 * (1 - t)); // rest at both ends
203
+ samples.push({ t, offsetX: rand() * amp * fade, offsetY: rand() * amp * fade });
204
+ }
205
+ return { samples };
206
+ ```
207
+
208
+ **Inertia.** A trailing part that lags the body and catches up — follow-through
209
+ without a second clip.
210
+
211
+ ```js
212
+ const n = inputs.sampleCount, lag = 0.12;
213
+ const ease = (t) => 1 - Math.pow(1 - t, 3);
214
+ const samples = [];
215
+ for (let i = 0; i < n; i++) {
216
+ const t = i / (n - 1);
217
+ const behind = ease(Math.max(0, t - lag) / (1 - lag));
218
+ samples.push({ t, offsetX: (1 - behind) * -0.1 * inputs.canvasWidth });
219
+ }
220
+ return { samples };
221
+ ```
222
+
223
+ **Arc.** Straight-line travel looks mechanical. Drive one axis linearly and the
224
+ other as a shallow parabola over the same window; both fold as `add`.
225
+
226
+ ```js
227
+ const n = inputs.sampleCount;
228
+ const dx = 0.25 * inputs.canvasWidth, rise = 0.06 * inputs.canvasHeight;
229
+ const samples = [];
230
+ for (let i = 0; i < n; i++) {
231
+ const t = i / (n - 1);
232
+ samples.push({ t, offsetX: -dx * (1 - t), offsetY: -rise * 4 * t * (1 - t) });
233
+ }
234
+ return { samples };
235
+ ```
236
+
237
+ **Pendulum.** A decaying rotation, in radians.
238
+
239
+ ```js
240
+ const n = inputs.sampleCount, a = 0.12, cycles = 2.5;
241
+ const samples = [];
242
+ for (let i = 0; i < n; i++) {
243
+ const t = i / (n - 1);
244
+ samples.push({ t, rotation: a * Math.cos(2 * Math.PI * cycles * t) * (1 - t) });
245
+ }
246
+ return { samples };
247
+ ```
248
+
249
+ **A loop with no visible join.** Any `loop` curve must return to its start, so
250
+ build it from a whole number of cycles of a periodic function.
251
+
252
+ ```js
253
+ const n = inputs.sampleCount, amp = 0.02 * inputs.canvasHeight;
254
+ const samples = [];
255
+ for (let i = 0; i < n; i++) {
256
+ const t = i / (n - 1);
257
+ samples.push({ t, offsetY: -amp * Math.sin(2 * Math.PI * t) });
258
+ }
259
+ return { samples };
260
+ ```
261
+
262
+ **Path draw-on.** `trimEnd` 0 to 1 on a `path` shape clip. Both trim channels
263
+ fold as `replace`, so one animation drives each and their windows must not
264
+ overlap.
265
+
266
+ ```js
267
+ return { curves: [{ property: "trimEnd",
268
+ keyframes: [{ t: 0, value: 0 }, { t: 1, value: 1, easing: "easeInOut" }] }] };
269
+ ```
270
+
271
+ **Wipe with a shaped edge.** A `wipeProgress` curve needs the mask on the same
272
+ animation, because direction and softness cannot be inferred:
273
+
274
+ ```json
275
+ {"role": "in", "preset": "custom", "durationMs": 700,
276
+ "mask": {"direction": "left", "softness": 0.1},
277
+ "curves": [{"property": "wipeProgress", "keyframes": [
278
+ {"t": 0, "value": 0}, {"t": 0.6, "value": 0.8, "easing": "easeOut"},
279
+ {"t": 1, "value": 1}]}]}
280
+ ```
281
+
282
+ ## Failure modes
283
+
284
+ - A body returning nothing, or both shapes, fails the bake with the reason. Read
285
+ it: the bake returns the error and the body's logs rather than throwing.
286
+ - A property name outside the channel list fails the bake and names the ones it
287
+ will take.
288
+ - A curve driving `wipeProgress` with no `mask` is refused.
289
+ - An easing string the grammar cannot read eases linearly and shows up as
290
+ `unknown_easing` on `validate_timeline`. It is `easeOut`, not `ease-out`.
291
+ - A window that does not fit the clip after its delay is `animation_exceeds_clip`
292
+ — the motion is clamped or never runs.
293
+ - A runaway loop hits the 10-second ceiling. A curve generator is arithmetic over
294
+ a few hundred points; if it is slow, it is wrong.
295
+
296
+ ## Check the shape you wrote
297
+
298
+ Curves are the one place where reading the numbers is not enough. Use
299
+ `preview_timeline_frame` with a `range` across the animation's window and
300
+ `sheet: true`, which tiles the sweep into one labelled contact sheet — the way
301
+ to watch an overshoot actually overshoot rather than checking three instants
302
+ that all happen to look right.