@nodetool-ai/agents 0.8.4-a24.2 → 0.8.4
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.
- package/dist/.nodetool-build-stamp +1 -1
- package/dist/_skills/LICENSE-iart-motion-design-skills +21 -0
- package/dist/_skills/README.md +211 -0
- package/dist/_skills/api-agents/SKILL.md +58 -0
- package/dist/_skills/api-apps/SKILL.md +54 -0
- package/dist/_skills/api-assets/SKILL.md +66 -0
- package/dist/_skills/api-collections/SKILL.md +33 -0
- package/dist/_skills/api-games/SKILL.md +81 -0
- package/dist/_skills/api-media/SKILL.md +131 -0
- package/dist/_skills/api-memory/SKILL.md +72 -0
- package/dist/_skills/api-models/SKILL.md +78 -0
- package/dist/_skills/api-scripts/SKILL.md +79 -0
- package/dist/_skills/api-settings/SKILL.md +49 -0
- package/dist/_skills/api-sketches/SKILL.md +61 -0
- package/dist/_skills/api-storyboards/SKILL.md +94 -0
- package/dist/_skills/api-timelines/SKILL.md +151 -0
- package/dist/_skills/api-web/SKILL.md +57 -0
- package/dist/_skills/api-workflows/SKILL.md +123 -0
- package/dist/_skills/beat-sync-editing/SKILL.md +208 -0
- package/dist/_skills/caption-titles/SKILL.md +94 -0
- package/dist/_skills/color-motion/SKILL.md +185 -0
- package/dist/_skills/commercial-beat-sheet/SKILL.md +338 -0
- package/dist/_skills/elevenlabs-audio-prompting/SKILL.md +163 -0
- package/dist/_skills/explainer-storyboard/SKILL.md +63 -0
- package/dist/_skills/flux-2-klein-prompting/SKILL.md +107 -0
- package/dist/_skills/frame-composition/SKILL.md +291 -0
- package/dist/_skills/game-direction/SKILL.md +99 -0
- package/dist/_skills/gpt-image-2-prompting/SKILL.md +161 -0
- package/dist/_skills/hailuo-prompting/SKILL.md +107 -0
- package/dist/_skills/kling-video-prompting/SKILL.md +188 -0
- package/dist/_skills/launch-commercial/SKILL.md +267 -0
- package/dist/_skills/launch-kit/SKILL.md +120 -0
- package/dist/_skills/logo-reveal/SKILL.md +225 -0
- package/dist/_skills/manimgx/SKILL.md +123 -0
- package/dist/_skills/minimax-h3-prompting/SKILL.md +134 -0
- package/dist/_skills/motion-ad/SKILL.md +281 -0
- package/dist/_skills/motion-background/SKILL.md +145 -0
- package/dist/_skills/motion-curves/SKILL.md +302 -0
- package/dist/_skills/motion-direction/SKILL.md +259 -0
- package/dist/_skills/motion-graphics/SKILL.md +217 -0
- package/dist/_skills/motion-principles/SKILL.md +255 -0
- package/dist/_skills/music-video-treatment/SKILL.md +40 -0
- package/dist/_skills/nano-banana-pro-prompting/SKILL.md +133 -0
- package/dist/_skills/native-game/SKILL.md +310 -0
- package/dist/_skills/native-game-reference/SKILL.md +879 -0
- package/dist/_skills/nodetool-3d-scene/SKILL.md +110 -0
- package/dist/_skills/nodetool-api-reference/SKILL.md +338 -0
- package/dist/_skills/nodetool-app-builder/SKILL.md +291 -0
- package/dist/_skills/nodetool-browser-agent/SKILL.md +119 -0
- package/dist/_skills/nodetool-chat-cli/SKILL.md +184 -0
- package/dist/_skills/nodetool-custom-node-developer/SKILL.md +294 -0
- package/dist/_skills/nodetool-deployment/SKILL.md +195 -0
- package/dist/_skills/nodetool-js-scripting/SKILL.md +223 -0
- package/dist/_skills/nodetool-model-provider-config/SKILL.md +348 -0
- package/dist/_skills/nodetool-rag-indexing/SKILL.md +153 -0
- package/dist/_skills/nodetool-sketch/SKILL.md +138 -0
- package/dist/_skills/nodetool-skill-author/SKILL.md +125 -0
- package/dist/_skills/nodetool-troubleshooter/SKILL.md +370 -0
- package/dist/_skills/nodetool-video-post/SKILL.md +149 -0
- package/dist/_skills/nodetool-workflow-builder/SKILL.md +295 -0
- package/dist/_skills/product-commercial/SKILL.md +132 -0
- package/dist/_skills/qwen-image-prompting/SKILL.md +122 -0
- package/dist/_skills/script-video/SKILL.md +106 -0
- package/dist/_skills/seedance-2-prompting/SKILL.md +194 -0
- package/dist/_skills/seedream-prompting/SKILL.md +117 -0
- package/dist/_skills/short-film/SKILL.md +123 -0
- package/dist/_skills/stable-audio-prompting/SKILL.md +130 -0
- package/dist/_skills/storyboard-core/SKILL.md +716 -0
- package/dist/_skills/timeline-edit-ops/SKILL.md +736 -0
- package/dist/_skills/trailer-template/SKILL.md +291 -0
- package/dist/_skills/ugc-video/SKILL.md +186 -0
- package/dist/_skills/veo-3-prompting/SKILL.md +140 -0
- package/dist/_skills/video-audio-continuity/SKILL.md +90 -0
- package/dist/_skills/video-clone/SKILL.md +96 -0
- package/dist/_skills/video-workflow/SKILL.md +119 -0
- package/dist/_skills/wan-2-6-prompting/SKILL.md +107 -0
- package/dist/app-build/bridge.d.ts.map +1 -1
- package/dist/app-build/bridge.js +44 -17
- package/dist/app-build/bridge.js.map +1 -1
- package/dist/capabilities/apps.d.ts.map +1 -1
- package/dist/capabilities/apps.js +55 -13
- package/dist/capabilities/apps.js.map +1 -1
- package/dist/capabilities/apps.specs.d.ts.map +1 -1
- package/dist/capabilities/apps.specs.js +5 -3
- package/dist/capabilities/apps.specs.js.map +1 -1
- package/dist/capabilities/game-asset-browser.d.ts.map +1 -1
- package/dist/capabilities/game-asset-browser.js +6 -1
- package/dist/capabilities/game-asset-browser.js.map +1 -1
- package/dist/capabilities/game.d.ts +1 -1
- package/dist/capabilities/game.d.ts.map +1 -1
- package/dist/capabilities/game.js +71 -56
- package/dist/capabilities/game.js.map +1 -1
- package/dist/capabilities/game.specs.js +1 -1
- package/dist/capabilities/game.specs.js.map +1 -1
- package/dist/capabilities/game3d.d.ts.map +1 -1
- package/dist/capabilities/game3d.js +14 -19
- package/dist/capabilities/game3d.js.map +1 -1
- package/dist/capabilities/gameAssertions.d.ts +52 -0
- package/dist/capabilities/gameAssertions.d.ts.map +1 -0
- package/dist/capabilities/gameAssertions.js +32 -0
- package/dist/capabilities/gameAssertions.js.map +1 -0
- package/dist/capabilities/models.d.ts.map +1 -1
- package/dist/capabilities/models.js +9 -0
- package/dist/capabilities/models.js.map +1 -1
- package/dist/capabilities/storyboards.d.ts.map +1 -1
- package/dist/capabilities/storyboards.js +36 -11
- package/dist/capabilities/storyboards.js.map +1 -1
- package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
- package/dist/capabilities/storyboards.specs.js +11 -1
- package/dist/capabilities/storyboards.specs.js.map +1 -1
- package/dist/capabilities/workflows.d.ts.map +1 -1
- package/dist/capabilities/workflows.js +1 -0
- package/dist/capabilities/workflows.js.map +1 -1
- package/dist/capabilities/workflows.specs.d.ts.map +1 -1
- package/dist/capabilities/workflows.specs.js +4 -3
- package/dist/capabilities/workflows.specs.js.map +1 -1
- package/dist/evals/surfaces/game.d.ts.map +1 -1
- package/dist/evals/surfaces/game.js +29 -1
- package/dist/evals/surfaces/game.js.map +1 -1
- package/dist/game-authoring.d.ts +120 -0
- package/dist/game-authoring.d.ts.map +1 -1
- package/dist/game-build-integrity.d.ts +12 -0
- package/dist/game-build-integrity.d.ts.map +1 -0
- package/dist/game-build-integrity.js +78 -0
- package/dist/game-build-integrity.js.map +1 -0
- package/dist/game-examples.d.ts +44 -3
- package/dist/game-examples.d.ts.map +1 -1
- package/dist/game-examples.js +60 -10
- package/dist/game-examples.js.map +1 -1
- package/dist/system-skills.d.ts +1 -1
- package/dist/system-skills.js +1 -1
- package/dist/tools/builtin-tools.d.ts.map +1 -1
- package/dist/tools/builtin-tools.js +2 -0
- package/dist/tools/builtin-tools.js.map +1 -1
- 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.
|