greenlight-motion 0.3.1 → 0.3.3

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/skill/SKILL.md CHANGED
@@ -18,7 +18,7 @@ You never write UI or animation code. If something is not in the format, it is n
18
18
  ## Setup (once per machine)
19
19
 
20
20
  ```
21
- npx greenlight-motion@0.3.1 doctor
21
+ npx greenlight-motion@0.3.3 doctor
22
22
  ```
23
23
 
24
24
  It checks Node 18+, Chrome or Edge, WebGPU and H.264 encoding, and prints a fix for anything missing. Do not install
@@ -39,7 +39,7 @@ animation presets (the same converter the GreenLight Motion connector uses in Cl
39
39
  keyframes rarely look as good. The format, the design rules and the catalogues: `skill/references/scene-script.md`.
40
40
 
41
41
  ```
42
- npx greenlight-motion@0.3.1 script <folder> --from scenes.json # or write <folder>/script.json, then: script <folder>
42
+ npx greenlight-motion@0.3.3 script <folder> --from scenes.json # or write <folder>/script.json, then: script <folder>
43
43
  ```
44
44
 
45
45
  Then `check` and `stills` (below), fix the SCRIPT and run `script` again — it rewrites `project.json`. A scene's
@@ -85,14 +85,14 @@ What the engine does there, so you don't learn it from a broken frame:
85
85
 
86
86
  ## Workflow
87
87
 
88
- 1. `npx greenlight-motion@0.3.1 init <folder>` — starts from a working example (title, subtitle, accent line, four
88
+ 1. `npx greenlight-motion@0.3.3 init <folder>` — starts from a working example (title, subtitle, accent line, four
89
89
  options). `--from file.json` starts from a project you have (for example one exported from GreenLight Dash).
90
90
  2. Edit `<folder>/project.json` — for changes to an EXISTING project prefer `apply` (below): it checks every edit and
91
91
  applies all or nothing. Put pictures, video, sound and fonts in `<folder>/assets/` and refer to them as
92
92
  `"asset:<file>"` (e.g. `"url": "asset:product.png"`).
93
- 3. `npx greenlight-motion@0.3.1 check <folder>` — fix everything it lists (missing files, template mistakes, blank
93
+ 3. `npx greenlight-motion@0.3.3 check <folder>` — fix everything it lists (missing files, template mistakes, blank
94
94
  frames, page errors), and read its notes: text the engine wraps where you did not break it. `--json` for machine output.
95
- 4. `npx greenlight-motion@0.3.1 stills <folder>` — renders frames and **a labelled contact sheet**. Open and look at
95
+ 4. `npx greenlight-motion@0.3.3 stills <folder>` — renders frames and **a labelled contact sheet**. Open and look at
96
96
  it before you go on: layout, readability, timing. Fix and repeat. `--at 0.5,1.2,3` for exact times.
97
97
  5. Hand it over: tell the user to open `<folder>/index.html` (Chrome or Edge), or `pack` it into one file, or
98
98
  `render` the MP4 yourself (`--fps 60 --motion-blur` for fast motion).
@@ -106,7 +106,7 @@ the error names the edit number with a fix. Ops:
106
106
  [{ "op": "set", "item": "title", "path": "textContent", "value": "Launch day" },
107
107
  { "op": "set", "project": true, "path": "videoSize.w", "value": 1080 },
108
108
  { "op": "unset", "item": "title", "path": "propertyKeyframes.opacity" },
109
- { "op": "keyframe", "item": "accent", "track": "translateX", "at": 50, "value": 120, "easing": "Power2.easeOut" },
109
+ { "op": "keyframe", "item": "accent", "track": "translateX", "at": 50, "value": 120, "easing": "Ease Out" },
110
110
  { "op": "add", "to": "clips", "value": { "id": "c9", "mediaType": "image", "url": "asset:a.png", "duration": 4 } },
111
111
  { "op": "add", "to": "channels", "value": { "id": "ch9" }, "at": 0 },
112
112
  { "op": "add", "to": { "channel": "ch9" }, "value": { "id": "it9", "clipId": "c9", "trimStart": 0, "trimEnd": 4 } },
@@ -157,7 +157,7 @@ editor's own normalizer), so write only what you mean.
157
157
  Full references (generated from the editor's own docs; ignore the `/api/` routes they mention):
158
158
  `skill/references/` — video-design, scene-script, video-projects, video-text, video-text-kinetic, video-text-presets, video-shapes, video-keyframes,
159
159
  video-camera, video-3d, video-effects, video-transitions, video-particles, video-paths, video-backgrounds, video-grid.
160
- Machine-readable lists: `npx greenlight-motion@0.3.1 schema effects | transitions | easings | paths | expressions`.
160
+ Machine-readable lists: `npx greenlight-motion@0.3.3 schema effects | transitions | easings | paths | expressions`.
161
161
 
162
162
  ## Template
163
163
 
@@ -204,7 +204,7 @@ an absolute moment; `align` `sync` (default) / `start` / `end`; `offset` seconds
204
204
 
205
205
  The library is 77 hand-picked sounds in categories (whooshes and swooshes, transitions, clicks, pop-ups and
206
206
  notifications, data blips, typing and writing, foley, bubbles, impacts and bass drops, camera, gears, voice clips).
207
- `npx greenlight-motion@0.3.1 schema sounds` lists every one with its `category`, `use` (what it is for), `duration`
207
+ `npx greenlight-motion@0.3.3 schema sounds` lists every one with its `category`, `use` (what it is for), `duration`
208
208
  and `sync`. Pick by `category` and `use`, then by name.
209
209
 
210
210
  - **Where the files come from:** the GreenLight sound CDN. `build` downloads each sound a cue uses into
@@ -220,7 +220,7 @@ The page has **Notes**: the user marks what should change right on the frame (a
220
220
  and writes a note for each mark. Every note carries the moment (seconds and frame), the mark in composition pixels
221
221
  and the layers under it (item ids in project.json), so you change exactly that and nothing else.
222
222
 
223
- - **They review on this computer:** run `npx greenlight-motion@0.3.1 review <folder>` and give them the URL it prints
223
+ - **They review on this computer:** run `npx greenlight-motion@0.3.3 review <folder>` and give them the URL it prints
224
224
  (it keeps running; `--open` opens their browser, only when they ask). Their notes land in `<folder>/review/`: read
225
225
  `review/NOTES.md`, look at the `at-<t>s.png` pictures (the marks are numbered), make the changes (`apply`), `build`
226
226
  again and tell them to reload the page.
@@ -249,11 +249,11 @@ that fails (`plugin`); look at `stills` for the look. A package that carries its
249
249
 
250
250
  The same project opens in GreenLight Dash (the desktop editor this engine comes from) for full editing: the page's
251
251
  edit note offers **Open in GreenLight Dash** (it imports the folder as a new project on the open board; a page with
252
- changes saves a `.glvideo`). `npx greenlight-motion@0.3.1 pack <folder> --glvideo` writes that file yourself: ONE
252
+ changes saves a `.glvideo`). `npx greenlight-motion@0.3.3 pack <folder> --glvideo` writes that file yourself: ONE
253
253
  `.glvideo` (the project + its files) that opens in GreenLight Dash on a double-click, or dropped on its board. Hand it
254
254
  over when the user wants to keep editing in the app. A
255
255
  GreenLight Dash project comes the other way through the app's **Export for agents** — a ready folder like the one
256
- `init` makes, with the options the author ticked. `npx greenlight-motion@0.3.1 init <folder> --from <its project.json>`
256
+ `init` makes, with the options the author ticked. `npx greenlight-motion@0.3.3 init <folder> --from <its project.json>`
257
257
  starts from one too. Never tell the user they must install it: the page plays, changes options and renders on its own.
258
258
 
259
259
  ## Files
@@ -69,7 +69,7 @@ matte, masks, particles, 3D) goes on from `project.json`: SKILL.md, "Beyond the
69
69
  `startAt` (s into the file) and `volume` (0–100, default muted). No entrance unless you set `enter`.
70
70
  - **Keyframes on any element** — your own motion: `"keyframes": { "x": [[0, -400], [0.8, 0, "Smooth Glide"]], "scale": [[0, 0.9], [2, 1.05]] }`
71
71
  — tracks x, y (px offsets from `at`), scale, rotate (deg), opacity (0–1); each key [seconds into the element, value, easing of the
72
- segment after it — Smooth Glide, AE Smooth, Expo Out, Back Out, Elastic, Linear …]. They replace an entrance's keys on the same track;
72
+ segment after it — Smooth Glide, AE Smooth, Expo Out, Power, Punch, Apple Out, Back Out, Elastic, Linear …]. They replace an entrance's keys on the same track;
73
73
  one key holds that value for the whole element.
74
74
  - **Every element**: `start` / `end` (s within its scene; default: staggered 0.12 s, to the scene's end), `enter` / `exit` (keyframe presets, `direction` up | down | left | right
75
75
  for directional ones), `effects`: [{ "id", "params"? }], `sound` (played at its start), `id`, `label` (its name in the page's options), `opacity` (0–1),
@@ -126,7 +126,7 @@ the first version and check every version against it.
126
126
 
127
127
  **Zoom feels** (`camera.feel`): `studio` Studio · `calm` Calm · `precise` Precise · `lively` Lively · `follow` Follow. Full camera layers (paths, Smart Zoom on a recording's clicks, the drawn cursor): references/video-camera.md.
128
128
 
129
- **Overlay effects** (`effects`): `glow`, ★ `bloom`, `auraBlur`, `gradientBeam`, ★ `gradientSweep`, `fractalNoise`, ★ `rgbSplit`, `glitch`, `scanlines`, `flashBlink`, `vhs`, `fisheye`, `opticsCompensation`, `vignette`, ★ `echo`, `timeshift`, `radialBlur`, ★ `radialFastBlur`, `sharpen`, `mirror`, `waveWarp`, `warpStyles`, `shockwave`, `waveLines`, `waveRipple`, `fill`, `shapeRulers`, `copyColor`, `curves`, `turbulentDisplace`, `alphaChoker`, `extract`, ★ `colorama`, `posterize`, ★ `halftone`, ★ `fastCameraWiggle`, ★ `lightRays`, `longShadow`, `outlineBands`, `kaleidoscope`, `crtMatrix`, `pixelate`, `vectorFieldWarp`, `pixelStretch`, `neonGlow`, `edgeDetect`, `doubleExposure`, ★ `filmStock`, `fastBlur`, `gridLines`, `strip`, `ascii`, `objectDither`, `objectIsolines`, `objectWindow`, `objectSlitScan`, `objectCurtain`, `objectGrid`, `objectContour`, `objectNodes`, `objectLinks`, `objectFacets`, `objectMeasure`
129
+ **Overlay effects** (`effects`): `glow`, ★ `bloom`, `auraBlur`, `gradientBeam`, ★ `gradientSweep`, `gradientGlow`, `fractalNoise`, ★ `rgbSplit`, `glitch`, `scanlines`, `flashBlink`, `vhs`, `fisheye`, `opticsCompensation`, `vignette`, ★ `echo`, `timeshift`, `radialBlur`, ★ `radialFastBlur`, `sharpen`, `mirror`, `waveWarp`, `warpStyles`, `shockwave`, `waveLines`, `waveRipple`, `fill`, `shapeRulers`, `copyColor`, `curves`, `turbulentDisplace`, `alphaChoker`, `extract`, ★ `colorama`, `posterize`, ★ `halftone`, ★ `fastCameraWiggle`, ★ `lightRays`, `longShadow`, `outlineBands`, `kaleidoscope`, `crtMatrix`, `pixelate`, `vectorFieldWarp`, `pixelStretch`, `neonGlow`, `edgeDetect`, `doubleExposure`, ★ `filmStock`, `fastBlur`, `gridLines`, `strip`, `ascii`, `objectDither`, `objectIsolines`, `objectWindow`, `objectSlitScan`, `objectCurtain`, `objectGrid`, `objectContour`, `objectNodes`, `objectLinks`, `objectFacets`, `objectMeasure`
130
130
 
131
131
  **Layer effects** (`effects`): `mockupFrame`, ★ `runningGlow`, `depthMap`, `depth3d`, `depthScan`, `fresnelRim`, ★ `motionBlur`, `posterizeTime`, ★ `motionTrail`, `autoFill`, ★ `repetile`, `regionDistort`, `bend`, `cornerPin`, `meshWarp`, `bezierWarp`, `rig`, ★ `droste`, `pryzm`, ★ `glass`, `chromaKey`, `layerMask`
132
132
 
@@ -34,10 +34,13 @@ and check every version against them.
34
34
 
35
35
  ## Easing by intent
36
36
 
37
- - Entrances: `AE Out` or `Expo Out` (fast start, soft landing). Camera and big moves: `AE Smooth`.
37
+ - Entrances: `AE In (arrive)`, `Expo Out` or `Power` (fast start, soft landing); UI panels and drawers: `Apple Out`.
38
+ Camera and big moves: `AE Smooth`.
39
+ - Scene changes on the beat, a logo hit, a screen swipe: `Punch` (slow wind-up, hard hit, long tail).
38
40
  - A layer that passes through (in, glide, out): `Smooth Glide` with the `glide` expression — the Glide keyframe
39
41
  presets build it (video-keyframes.md).
40
- - Playful arrivals: `Back Out` or `Overshoot`. Exits: `Ease In`.
42
+ - Playful arrivals: `Back Out` or `Overshoot`; a real spring: the `spring` expression (Snappy for buttons, icons and
43
+ pop-ins, Gentle for mascots, big cards and the camera). Exits: `Ease In` or `AE Out (leave)`.
41
44
  - `Linear` only for constant motion: tickers, rotation, progress along a path.
42
45
 
43
46
  ## Text animation
@@ -149,7 +149,7 @@ missing ones take the default shown, and out-of-range values are clamped on load
149
149
  "revealDuration": 0.6, // seconds, for ONE cell
150
150
  "revealStagger": 0.05, // seconds between cells (negative runs the wave backwards)
151
151
  "revealStaggerBy": "index", // index | ring | distance | random
152
- "revealEase": "Power2 Out",
152
+ "revealEase": "Ease Out",
153
153
  "revealOut": false, // cells leave again before the layer ends, in the same order
154
154
 
155
155
  // ── layout slots + morph (Grid Morph) ──
@@ -168,7 +168,7 @@ missing ones take the default shown, and out-of-range values are clamped on load
168
168
  "layoutMix": 0, // 0…layouts.length-1 — keyframe `gr:layoutMix` to morph; integer = exactly that slot (single layout evaluated); a Hold key hard-cuts
169
169
  "morphStagger": 0.35, // 0–0.9 — spreads the move across cell ranks (0 = all cells slide together)
170
170
  "morphStaggerBy": "index", // index | ring | distance | random — ranked on the layout being LEFT
171
- "morphEase": "Power2 Smooth",// per-cell ramp (TransitionEasing name)
171
+ "morphEase": "Smooth",// per-cell ramp (TransitionEasing name)
172
172
  "morphArc": 60, // px — bulges each cell's travel outward mid-move (negative implodes); exactly zero at both endpoints
173
173
  "morphSpin": 0 // whole turns added over the blend; integers land back on themselves
174
174
  }
@@ -19,9 +19,9 @@
19
19
 
20
20
  Every timeline item can animate its visual properties over the clip's duration. Each property gets its **own independent track** of keys under `propertyKeyframes` — an opacity fade on a different rhythm than a slow zoom, a rotation eased with a bounce while the scale stays smooth, etc. Tracks are always separate — there is no merged, all-properties-per-key form.
21
21
 
22
- Positions are always **0–100, percent of the clip's played duration** (not seconds). `value` is stored as a **string**. Easing values come from `GET /api/video-editor/capabilities` → `easings.keyframe.values` (e.g. `Linear`, `Smooth`, `Ease In`, `Ease Out`, `AE In`, `AE Smooth`, `AE Out`, `Power2 Smooth`, `Expo Out`, `Back Smooth`, `Bounce Out`, `Elastic`, …); an easing set on a key shapes the segment **after** that key. The `AE *` values are the After Effects-style bezier curves shared with MotionFast (`AE Smooth` is the classic slow-start/soft-stop AE feel — a good default for cinematic moves). Every easing still comes to a dead stop on its key. For more live energy — a move that lands soft and keeps drifting on, a shot that never sits still between keys — add the `glide` expression type on top of the track (`propertyKeyframeFx.<track> = {"type":"glide","params":{"fast":0.1,"slow":1,"weight":40,"delay":0}}`, see **Glide** under [Track expressions](video-keyframes.md#track-expressions-propertykeyframefx)). Two owner curves sit right after `Smooth`: **`Smooth Glide`** (`cubic-bezier(0.2111, 0.7721, 0.8541, 0.112)` — arrives fast, glides slowly through the middle, leaves fast) and **`Glide Easing Stop`** (`cubic-bezier(0, 0.9125, 1, -0.0338)` — jumps in, all but stops, jumps out). **In and Out pass (the Glide presets):** one track, `0 %` → off-frame on the entry side (`xPercent`/`yPercent` ±100) eased `Smooth Glide`, last key at **`80 %`** → ∓150 (`Linear`), plus `propertyKeyframeFx.<track> = {"type":"glide","params":{"fast":0.1,"slow":0.6,"weight":25,"delay":0}}` — the glide tail lags in seconds, so ±100 → ∓100 at 100 % would still be on screen at the layer's end; these keys make it leave (−130…−145 % over 1.5–4 s layers) with the slow middle near the centre. Five expressive curves from the MotionFast template catalogue are also available: `Natural` (punchy ease-in-out), `Slow Down` (pure decelerate), `Overshoot` (shoots past the target, settles slowly), `Impulse` (anticipation dip backwards before launching), and `Swing` (wind-up, then a springy arrival) — use these to reproduce classic template motion on any layer. A **single-key track holds its value constant** for the whole clip. Transform origin is one item-level field, `keyframeTransformOrigin` (`"center"`, `"top"`, `"bottom"`, `"left"`, `"right"`, a corner such as `"bottom left"`, or `"x% y%"`): the point every rotation (static and keyed, 2D and 3D) and the keyed scale turn about, while `panX`/`panY`/`panZ` stay the layer's resting centre — see [Turning a flat layer in 3D](video-3d.md#flat-layers-in-the-3d-scene--itemshadowplate).
22
+ Positions are always **0–100, percent of the clip's played duration** (not seconds). `value` is stored as a **string**. Easing values come from `GET /api/video-editor/capabilities` → `easings.keyframe.values` (e.g. `Linear`, `Smooth`, `Ease In`, `Ease Out`, `AE In (arrive)`, `AE Smooth`, `AE Out (leave)`, `Expo Out`, `Power`, `Punch`, `Back Smooth`, `Bounce Out`, `Elastic`, …); an easing set on a key shapes the segment **after** that key. The `AE …` values are the After Effects-style bezier curves shared with MotionFast (`AE Smooth` is the classic slow-start/soft-stop AE feel — a good default for cinematic moves); AE names a curve by the side of the key, so `AE In (arrive)` starts fast and brakes into the key and `AE Out (leave)` starts slow and speeds away. Three more curves for decisive moves: **`Power`** (a hard decelerate, `cubic-bezier(0.23, 1, 0.32, 1)` — enters, landings), **`Punch`** (`cubic-bezier(0.7, 0, 0.1, 1)` — slow wind-up, hard hit, long tail: scene changes on the beat, a logo hit, a screen swipe) and **`Apple Out`** (`cubic-bezier(0.32, 0.72, 0, 1)` — full speed at once, then a long soft stop: UI panels, drawers, a camera landing). Old names still load and are mapped to the curve that replaced them (`easings.keyframe.retired`: `Power2 Out` → `Ease Out`, `Power3 Out` → `Cubic Out`, `Power4 Out` → `Power`, `Circ Out` → `Slow Down`, `Cubic Smooth` → `Natural` …); write the current names. Every easing still comes to a dead stop on its key. For a spring that overshoots and settles in real seconds, put the **`spring`** expression type on the track (`propertyKeyframeFx.<track> = {"type":"spring","params":{"amp":100,"freq":1.6,"damping":0.55}}`, see `spring` under [Track expressions](video-keyframes.md#track-expressions-propertykeyframefx)). For more live energy — a move that lands soft and keeps drifting on, a shot that never sits still between keys — add the `glide` expression type on top of the track (`propertyKeyframeFx.<track> = {"type":"glide","params":{"fast":0.1,"slow":1,"weight":40,"delay":0}}`, see **Glide** under [Track expressions](video-keyframes.md#track-expressions-propertykeyframefx)). Two owner curves sit right after `Smooth`: **`Smooth Glide`** (`cubic-bezier(0.2111, 0.7721, 0.8541, 0.112)` — arrives fast, glides slowly through the middle, leaves fast) and **`Glide Easing Stop`** (`cubic-bezier(0, 0.9125, 1, -0.0338)` — jumps in, all but stops, jumps out). **In and Out pass (the Glide presets):** one track, `0 %` → off-frame on the entry side (`xPercent`/`yPercent` ±100) eased `Smooth Glide`, last key at **`80 %`** → ∓150 (`Linear`), plus `propertyKeyframeFx.<track> = {"type":"glide","params":{"fast":0.1,"slow":0.6,"weight":25,"delay":0}}` — the glide tail lags in seconds, so ±100 → ∓100 at 100 % would still be on screen at the layer's end; these keys make it leave (−130…−145 % over 1.5–4 s layers) with the slow middle near the centre. Five expressive curves from the MotionFast template catalogue are also available: `Natural` (punchy ease-in-out), `Slow Down` (pure decelerate), `Overshoot` (shoots past the target, settles slowly), `Impulse` (anticipation dip backwards before launching), and `Swing` (wind-up, then a springy arrival) — use these to reproduce classic template motion on any layer. A **single-key track holds its value constant** for the whole clip. Transform origin is one item-level field, `keyframeTransformOrigin` (`"center"`, `"top"`, `"bottom"`, `"left"`, `"right"`, a corner such as `"bottom left"`, or `"x% y%"`): the point every rotation (static and keyed, 2D and 3D) and the keyed scale turn about, while `panX`/`panY`/`panZ` stay the layer's resting centre — see [Turning a flat layer in 3D](video-3d.md#flat-layers-in-the-3d-scene--itemshadowplate).
23
23
 
24
- **The easing catalogue.** `GET /api/video-editor/capabilities` → `easings` carries both vocabularies with the actual curve behind every name, so a look can be matched or carried into another tool without guessing. `easings.keyframe` covers keyframe and transition easings: `values` (all of them), `transitionValues` (the subset a transition accepts — `Custom` and `Hold` need per-key data and are rejected there), and `curves[]`. `easings.motionTemplate` covers the camelCase names a template's ease field takes (`aeSmooth`, `power2`, …). In `curves[]`, `kind` says which field IS the curve — `bezier` → `cubicBezier` `[x1,y1,x2,y2]` plus a ready-to-paste `css`, `sampled` → `samples`, the curve at t = 0, 0.01 … 1 (values past 0..1 are real overshoot; as CSS that is `linear(<samples>)`). `gsap` names the ease that plays, and `keyframeEquivalent` / `motionTemplateEquivalent` link the same curve across the two vocabularies — only where the curves genuinely match, so treat a missing link as "these are different curves that happen to share a name" (template `smooth` is not keyframe `Smooth`; template `power2` is keyframe `Ease Out`). A `sampled` entry's `cubicBezier` (when marked `bezierIsApproximate`) is only the closest cubic — `bezierMaxError` says how far off, up to ~0.19 for `Circ Out`; match against the samples, not the handles. Two easings carry no curve: `Custom` reads the key's own `bezier: [x1,y1,x2,y2]` (write both together or it falls back to Smooth), and `Hold` steps to the next key.
24
+ **The easing catalogue.** `GET /api/video-editor/capabilities` → `easings` carries both vocabularies with the actual curve behind every name, so a look can be matched or carried into another tool without guessing. `easings.keyframe` covers keyframe and transition easings: `values` (all of them), `transitionValues` (the subset a transition accepts — `Custom` and `Hold` need per-key data and are rejected there), `retired` (old name → the value that replaced it; still accepted on load and by the API) and `curves[]`. `easings.motionTemplate` covers the camelCase names a template's ease field takes (`aeSmooth`, `power2`, …). In `curves[]`, `kind` says which field IS the curve — `bezier` → `cubicBezier` `[x1,y1,x2,y2]` plus a ready-to-paste `css`, `sampled` → `samples`, the curve at t = 0, 0.01 … 1 (values past 0..1 are real overshoot; as CSS that is `linear(<samples>)`). `gsap` names the ease that plays, and `keyframeEquivalent` / `motionTemplateEquivalent` link the same curve across the two vocabularies — only where the curves genuinely match, so treat a missing link as "these are different curves that happen to share a name" (template `smooth` is not keyframe `Smooth`; template `power2` is keyframe `Ease Out`). A `sampled` entry's `cubicBezier` (when marked `bezierIsApproximate`) is only the closest cubic — `bezierMaxError` says how far off, up to ~0.13 for `Expo Smooth`; match against the samples, not the handles. Two easings carry no curve: `Custom` reads the key's own `bezier: [x1,y1,x2,y2]` (write both together or it falls back to Smooth), and `Hold` steps to the next key.
25
25
 
26
26
  ```json
27
27
  {
@@ -78,9 +78,10 @@ An AE-style expression layered on top of a track's keyframed value. Stored per t
78
78
  }
79
79
  ```
80
80
 
81
- `elastic` and `bounce` **reshape the move itself** — they are not rings appended after a key. Each keyframe transition is treated as a step of size `value[i] − value[i−1]` and replayed through a profile normalized to that segment's own duration, so the layer launches at the previous key, overshoots the target and settles into it as **one continuous motion**. Consequences worth knowing: the key's own `easing` is superseded on a track carrying one of these (use `amp` to mix back toward it), the shape is identical for a 0.3s and a 3s move, and it works on the commonest track of all — two keys at 0 and 100 — where a post-key ring would have nowhere to play.
81
+ `elastic`, `bounce` and `spring` **reshape the move itself** — they are not rings appended after a key. Each keyframe transition is treated as a step of size `value[i] − value[i−1]` that starts at the previous key, so the layer launches there, overshoots the target and settles into it as **one continuous motion**. Consequences worth knowing: the key's own `easing` is superseded on a track carrying one of these (use `amp` to mix back toward it), and it works on the commonest track of all — two keys at 0 and 100 — where a post-key ring would have nowhere to play. `elastic` and `bounce` are normalized to each segment's own duration (the shape is identical for a 0.3s and a 3s move); `spring` runs in seconds instead.
82
82
 
83
83
  - **`elastic`** (`amp` % strength 0–300, `freq` wobbles **across the move** 0.3–5, `decay` 0.5–15) — launches, crosses the target at `1/(4·freq)` of the move, overshoots by ≈`e^(−decay/(2·freq))`, then wobbles in. Defaults (100/1.1/5) give a ~13% overshoot that has settled to <1% by the key. A leftover wobble carries into the next segment as follow-through. Lower `freq` for a soft landing with a single late overshoot; raise it for a fast arrival with several wobbles.
84
+ - **`spring`** (`amp` % strength 0–300, `freq` 0.1–10 Hz, `damping` ratio 0.05–1) — a real damped spring in **seconds**, released at rest from the key before: `x = a + (b−a)·(1 − e^(−ζωt)·(cos ω_d t + ζω/ω_d·sin ω_d t))`, `ω = 2π·freq`, `ω_d = ω·√(1−ζ²)`, `t` = seconds since the move's first key. It peaks at `1 + e^(−ζπ/√(1−ζ²))` of the step and settles in ≈ `4/(ω·ζ)` s whatever the gap to the next key, so a short move and a long one ring at the same rate (Elastic stretches instead); the move's END key only says when the next step starts. A ring not settled carries into the next step. Presets (`keyframeExpressions.types[].presets`, they set `freq` + `damping` only): `snappy` 1.6 Hz / 0.55 — ~13 % overshoot, settled in 0.7 s: buttons, icons, pop-ins (the defaults); `gentle` 1 Hz / 0.8 — 1.5 % overshoot: mascots, big cards, the camera. `damping` 1 never overshoots. Leave each step ~1 s before the next key so the ring has settled.
84
85
  - **`bounce`** (`amp` % strength, `elasticity` bounciness 5–80%, `bounces` 1–10) — falls onto the target and rebounds back the way it came in decaying hops. Self-normalizing: the fall plus every hop are scaled to fit the move exactly, so it always settles **on** the key and needs no gravity or hop-duration knob. Higher bounciness spends more of the move hopping (so it lands earlier); it never passes the target.
85
86
  - **`wiggle`** (`amp` in the property's own units, `freq` Hz) — deterministic smooth jitter **added around** the keyed value across the whole clip (this one is additive and leaves the base motion intact), seeded per item+track so copies don't move in sync. A single-key (constant) track plus wiggle is the cheapest "handheld"/idle-motion recipe.
86
87
  - **`glide`** (`fast` response 0.01–3 s, `slow` tail length 0.05–8 s, `weight` tail share 0–100 %, `delay` tail delay 0–3 s) — **softens** the keyed motion (its easing included): a move lands soft and keeps creeping on in the direction it travelled, never past the key and never back. Presets (also in `keyframeExpressions.types[].presets`): `soft` 0.1/0.6/25/0 (the defaults), `floaty` 0.1/1/40/0, `trailing` 0.1/0.45/25/0.1, `endless` 0.1/2/14/0. Same filter as `glide()` in a custom expression. See **Glide** below.
@@ -84,7 +84,7 @@ in the last frames.
84
84
  | [Center Burst](#center-burst) | The middle words burst out first and the others follow, wobbling into place; at the end they shrink away from the outside in | words | each word's centre | Elastic · Back In |
85
85
  | [Pendulum](#pendulum) | Words hang from their top edge and swing in like a pendulum until they settle; at the end they swing away | words | each word's centre | Elastic · Anticipate |
86
86
  | [Spin Pop](#spin-pop) | Each word spins a full turn in place as it grows in; at the end each spins back out | words | each word's centre | Back Out · Back In |
87
- | [Domino](#domino) | Words stand up on their bottom-left corner and drop flat with a bounce, one after another; at the end they fall over | words | each word's bottom-left corner | Bounce Out · Power2 In |
87
+ | [Domino](#domino) | Words stand up on their bottom-left corner and drop flat with a bounce, one after another; at the end they fall over | words | each word's bottom-left corner | Bounce Out · Ease In |
88
88
  | [Flip Pop](#flip-pop) | Words flip up into view in 3D, past flat and back; at the end they tip back and flip away | words | each word's centre | Overshoot · Anticipate |
89
89
  | [Card Turn](#card-turn) | Words turn to face you like cards, one by one, overshooting the turn; at the end they turn away | words | each word's centre | Overshoot · Anticipate |
90
90
  | [Slam Zoom](#slam-zoom) | Words slam down from huge, punch past their size and settle; at the end they fly back at the camera | words | each word's centre | Overshoot · Expo In |
@@ -93,7 +93,7 @@ in the last frames.
93
93
  | [Whip](#whip) | Words whip in from the left leaning into the move, snap past and settle; at the end they lean back and whip off to the right | words | each word's centre | Overshoot · Anticipate |
94
94
  | [Rubber Letters](#rubber-letters) | Letters spring out of thin slivers and wobble like rubber; at the end they stretch tall and squash flat | letters | each letter's centre | Elastic · Anticipate |
95
95
  | [Pop Letters](#pop-letters) | Letters pop up in random order, each with a little twist; at the end they swell and pop away at random | letters | each letter's centre | Overshoot · Anticipate |
96
- | [Drop Swing](#drop-swing) | Words drop in tilted from above and bounce as they land; at the end they fall out of the frame | words | each word's centre | Bounce Out · Power2 In |
96
+ | [Drop Swing](#drop-swing) | Words drop in tilted from above and bounce as they land; at the end they fall out of the frame | words | each word's centre | Bounce Out · Ease In |
97
97
  | [Coin Spin](#coin-spin) | Letters spin in like coins, one after another, and rock to a stop; at the end they spin away | letters | each letter's centre | Overshoot · Anticipate |
98
98
  | [Converge](#converge) | Words shrink down from far bigger, the outer ones first, and settle; at the end they burst outward | words | each word's centre | Expo Out · Expo In |
99
99
  | [Roll In](#roll-in) | Letters roll in from the right like wheels and rock back into place; at the end they roll off to the left | letters | each letter's centre | Overshoot · Back In |
@@ -215,7 +215,7 @@ Words stand up on their bottom-left corner and drop flat with a bounce, one afte
215
215
  "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "word", "stagger": 0.14, "duration": 1, "curve": "Bounce Out"},
216
216
  "effects": [{"pid": "p1", "type": "rotation", "angle": -90}, {"pid": "p2", "type": "opacity", "opacity": 0}] },
217
217
  { "aid": "a2", "name": "Fall out",
218
- "range": {"basedOn": "words", "drive": "time", "animate": "out", "around": "word", "stagger": 0.08, "duration": 0.6, "curve": "Power2 In"},
218
+ "range": {"basedOn": "words", "drive": "time", "animate": "out", "around": "word", "stagger": 0.08, "duration": 0.6, "curve": "Ease In"},
219
219
  "effects": [{"pid": "p3", "type": "rotation", "angle": 90}, {"pid": "p4", "type": "opacity", "opacity": 0}] }
220
220
  ],
221
221
  "textAnimatorPivotX": -100,
@@ -371,7 +371,7 @@ Words drop in tilted from above and bounce as they land; at the end they fall ou
371
371
  "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "word", "stagger": 0.08, "duration": 0.9, "curve": "Bounce Out"},
372
372
  "effects": [{"pid": "p1", "type": "position", "x": 0, "y": -120}, {"pid": "p2", "type": "rotation", "angle": 25}, {"pid": "p3", "type": "opacity", "opacity": 0}] },
373
373
  { "aid": "a2", "name": "Fall out",
374
- "range": {"basedOn": "words", "drive": "time", "animate": "out", "around": "word", "stagger": 0.04, "duration": 0.5, "curve": "Power2 In"},
374
+ "range": {"basedOn": "words", "drive": "time", "animate": "out", "around": "word", "stagger": 0.04, "duration": 0.5, "curve": "Ease In"},
375
375
  "effects": [{"pid": "p4", "type": "position", "x": 0, "y": 120}, {"pid": "p5", "type": "rotation", "angle": -25}, {"pid": "p6", "type": "opacity", "opacity": 0}] }
376
376
  ]
377
377
  }
@@ -87,7 +87,7 @@ Type on Shape has no preferred preset: pick by the shape the text needs.
87
87
 
88
88
  ```json
89
89
  { "aid": "a9", "name": "Out",
90
- "range": {"drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.04, "duration": 0.3, "curve": "Power2 In"},
90
+ "range": {"drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.04, "duration": 0.3, "curve": "Ease In"},
91
91
  "effects": [{"pid": "p19", "type": "opacity", "opacity": 0}] }
92
92
  ```
93
93
 
@@ -162,20 +162,20 @@ Letters type in as the word glides left under a chasing blue band and settles, t
162
162
  "range": {"basedOn": "charsNoSpace", "around": "all"},
163
163
  "effects": [{"pid": "p3", "type": "position", "x": 250, "y": 0}] },
164
164
  { "aid": "a4", "name": "Glide out",
165
- "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Power2 In"},
165
+ "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Ease In"},
166
166
  "effects": [{"pid": "p4", "type": "position", "x": -150, "y": 0}, {"pid": "p5", "type": "opacity", "opacity": 0}] }
167
167
  ],
168
168
  "propertyKeyframes": {
169
169
  "txa:a1:start": [
170
- { "position": 0, "value": "0", "easing": "AE In" }, // 0 s
170
+ { "position": 0, "value": "0", "easing": "AE In (arrive)" }, // 0 s
171
171
  { "position": 20, "value": "100", "easing": "Smooth" } // 1 s
172
172
  ],
173
173
  "txa:a2:offset": [
174
- { "position": 6, "value": "-58", "easing": "Power1 Out" }, // 0.3 s
174
+ { "position": 6, "value": "-58", "easing": "Sine Out" }, // 0.3 s
175
175
  { "position": 26, "value": "101", "easing": "Smooth" } // 1.3 s
176
176
  ],
177
177
  "txa:a3:amount": [
178
- { "position": 0, "value": "100", "easing": "AE In" }, // 0 s
178
+ { "position": 0, "value": "100", "easing": "AE In (arrive)" }, // 0 s
179
179
  { "position": 20, "value": "0", "easing": "Smooth" } // 1 s
180
180
  ]
181
181
  }
@@ -199,16 +199,16 @@ Letters type in under a chasing blue band as the word keeps gliding through the
199
199
  "range": {"basedOn": "charsNoSpace", "around": "all"},
200
200
  "effects": [{"pid": "p3", "type": "position", "x": 250, "y": 0}] },
201
201
  { "aid": "a4", "name": "Glide out",
202
- "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Power2 In"},
202
+ "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Ease In"},
203
203
  "effects": [{"pid": "p4", "type": "position", "x": -150, "y": 0}, {"pid": "p5", "type": "opacity", "opacity": 0}] }
204
204
  ],
205
205
  "propertyKeyframes": {
206
206
  "txa:a1:start": [
207
- { "position": 0, "value": "0", "easing": "AE In" }, // 0 s
207
+ { "position": 0, "value": "0", "easing": "AE In (arrive)" }, // 0 s
208
208
  { "position": 18.6, "value": "100", "easing": "Smooth" } // 0.93 s
209
209
  ],
210
210
  "txa:a2:offset": [
211
- { "position": 2.8, "value": "-58", "easing": "Power1 Out" }, // 0.14 s
211
+ { "position": 2.8, "value": "-58", "easing": "Sine Out" }, // 0.14 s
212
212
  { "position": 22.6, "value": "101", "easing": "Smooth" } // 1.13 s
213
213
  ],
214
214
  "txa:a3:amount": [
@@ -227,13 +227,13 @@ Words spring in out of a hot green glow and overshoot into place as the line rec
227
227
  {
228
228
  "textAnimators": [
229
229
  { "aid": "a1", "name": "Recenter",
230
- "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "all", "stagger": 0, "duration": 1.8, "curve": "Power3 Out"},
230
+ "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "all", "stagger": 0, "duration": 1.8, "curve": "Cubic Out"},
231
231
  "effects": [{"pid": "p1", "type": "position", "x": 462.96, "y": 0}] },
232
232
  { "aid": "a2", "name": "Spring in",
233
233
  "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "word", "stagger": 0.15, "duration": 1.1, "curve": "Overshoot"},
234
234
  "effects": [{"pid": "p2", "type": "position", "x": 129.63, "y": 157.41}, {"pid": "p3", "type": "scale", "scale": 78}] },
235
235
  { "aid": "a3", "name": "Focus in",
236
- "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "auto", "stagger": 0.15, "duration": 0.38, "curve": "Power2 Out"},
236
+ "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "auto", "stagger": 0.15, "duration": 0.38, "curve": "Ease Out"},
237
237
  "effects": [{"pid": "p4", "type": "opacity", "opacity": 0}, {"pid": "p5", "type": "blur", "amount": 25.93}] },
238
238
  { "aid": "a4", "name": "Hot glow",
239
239
  "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "auto", "stagger": 0.15, "duration": 0.65, "curve": "Sine Smooth"},
@@ -265,16 +265,16 @@ The same typing and band, but the word glides through the whole layer and all bu
265
265
  "range": {"basedOn": "charsNoSpace", "around": "all"},
266
266
  "effects": [{"pid": "p3", "type": "position", "x": 250, "y": 0}] },
267
267
  { "aid": "a4", "name": "Glide out",
268
- "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Power2 In"},
268
+ "range": {"basedOn": "charsNoSpace", "drive": "time", "animate": "out", "around": "all", "stagger": 0, "duration": 0.5, "curve": "Ease In"},
269
269
  "effects": [{"pid": "p4", "type": "position", "x": -150, "y": 0}, {"pid": "p5", "type": "opacity", "opacity": 0}] }
270
270
  ],
271
271
  "propertyKeyframes": {
272
272
  "txa:a1:start": [
273
- { "position": 0, "value": "0", "easing": "AE In" }, // 0 s
273
+ { "position": 0, "value": "0", "easing": "AE In (arrive)" }, // 0 s
274
274
  { "position": 18.6, "value": "100", "easing": "Smooth" } // 0.93 s
275
275
  ],
276
276
  "txa:a2:offset": [
277
- { "position": 2.8, "value": "-58", "easing": "Power1 Out" }, // 0.14 s
277
+ { "position": 2.8, "value": "-58", "easing": "Sine Out" }, // 0.14 s
278
278
  { "position": 22.6, "value": "101", "easing": "Smooth" } // 1.13 s
279
279
  ],
280
280
  "txa:a3:amount": [
@@ -1039,7 +1039,7 @@ A box sits under each word while it is spoken, dark text on top. Plays: while ea
1039
1039
  {
1040
1040
  "textAnimators": [
1041
1041
  { "aid": "a1", "name": "Spoken box",
1042
- "range": {"basedOn": "words", "drive": "voice", "animate": "active", "duration": 0.08, "curve": "Power2 Out", "fadeOut": 0.08},
1042
+ "range": {"basedOn": "words", "drive": "voice", "animate": "active", "duration": 0.08, "curve": "Ease Out", "fadeOut": 0.08},
1043
1043
  "effects": [{"pid": "p1", "type": "box", "color": "#33efab", "padX": 12, "padY": 4, "radius": 14, "grow": false}, {"pid": "p2", "type": "fill", "color": "#0b0d0c"}] }
1044
1044
  ]
1045
1045
  }
@@ -1109,7 +1109,7 @@ Words wait dimmed and light up as they are said. Plays: as each word is spoken.
1109
1109
  {
1110
1110
  "textAnimators": [
1111
1111
  { "aid": "a1", "name": "Not spoken yet",
1112
- "range": {"basedOn": "words", "drive": "voice", "animate": "in", "duration": 0.2, "curve": "Power2 Out"},
1112
+ "range": {"basedOn": "words", "drive": "voice", "animate": "in", "duration": 0.2, "curve": "Ease Out"},
1113
1113
  "effects": [{"pid": "p1", "type": "opacity", "opacity": 30}] }
1114
1114
  ]
1115
1115
  }
@@ -1137,7 +1137,7 @@ Words rise softly out of a blur as they are spoken. Plays: as each word is spoke
1137
1137
  {
1138
1138
  "textAnimators": [
1139
1139
  { "aid": "a1", "name": "Rise",
1140
- "range": {"basedOn": "words", "drive": "voice", "animate": "in", "duration": 0.45, "curve": "Power3 Out"},
1140
+ "range": {"basedOn": "words", "drive": "voice", "animate": "in", "duration": 0.45, "curve": "Cubic Out"},
1141
1141
  "effects": [{"pid": "p1", "type": "position", "x": 0, "y": 45}, {"pid": "p2", "type": "blur", "amount": 8}, {"pid": "p3", "type": "opacity", "opacity": 0}] }
1142
1142
  ]
1143
1143
  }
@@ -150,9 +150,9 @@ Orders {{{COUNTER:0-2,480; style=roll; duration=2.5; easing=expo_out}}} in {{{TI
150
150
 
151
151
  | Type | Options (defaults) |
152
152
  |---|---|
153
- | COUNTER | `style` odometer\|roll\|count (odometer) · `start` s (0) · `duration` s (1.8) · `easing` a TransitionEasing key in lowercase, e.g. `power3_out` (power3_out) · `width` fixed\|fit (fixed) · `digits` tabular\|natural, Count only (tabular) · `turns` 0–10 (3) · `lands` left\|right\|together (left) · `cascade` 0–90 % (45) · `direction` auto\|up\|down (auto) · `blur` 0–100 % (35) · `kf` a track id |
153
+ | COUNTER | `style` odometer\|roll\|count (odometer) · `start` s (0) · `duration` s (1.8) · `easing` a TransitionEasing key in lowercase, e.g. `cubic_out` (cubic_out) · `width` fixed\|fit (fixed) · `digits` tabular\|natural, Count only (tabular) · `turns` 0–10 (3) · `lands` left\|right\|together (left) · `cascade` 0–90 % (45) · `direction` auto\|up\|down (auto) · `blur` 0–100 % (35) · `kf` a track id |
154
154
  | TIMER | the same keys; defaults are a real clock: `style` count, `duration` = the timer's own span (05:00 → 300 s), `easing` linear |
155
- | CYCLE | `style` roll\|fade\|blur (roll) · `start` (0) · `hold` s per word (1.2) · `duration` transition s (0.45) · `easing` (power3_out) · `width` fit\|fixed (fit) · `direction` up\|down, roll only (up) · `loop` on\|off (on) |
155
+ | CYCLE | `style` roll\|fade\|blur (roll) · `start` (0) · `hold` s per word (1.2) · `duration` transition s (0.45) · `easing` (cubic_out) · `width` fit\|fixed (fit) · `direction` up\|down, roll only (up) · `loop` on\|off (on) |
156
156
  | SCRAMBLE | `start` (0) · `duration` (1.2) · `chars` symbols\|letters\|digits\|binary\|blocks (symbols) · `order` ltr\|rtl\|random (ltr) · `flicker` glyph changes/s (18) |
157
157
  | TIMECODE / FRAME | no options — `clip` and the format are the token body (`{{{TIMECODE:clip mm:ss}}}`) |
158
158
 
@@ -221,7 +221,7 @@ Rules the sanitizer enforces (anything else is dropped or clamped on load): `aid
221
221
  | `delay` | −60…600 s (0) | In: after the layer starts. Out: before it ends. Voice: after the word is spoken (negative = before). |
222
222
  | `stagger` | 0…10 s (0.06) | Time: between one unit's turn and the next. |
223
223
  | `duration` | 0…20 s (0.6) | How long each unit takes (0 = instant). For `active`/`stay` it is the fade-in. |
224
- | `curve` | the app's easing names (`Ease Out`) | Each unit's own easing: `Linear`, `Ease In/Out`, `Smooth`, `Power1–4 In/Out/Smooth`, `Sine/Cubic/Expo/Circ/Back In/Out/Smooth`, `Bounce Out`, `Elastic`, `Overshoot`, `Smooth Overshoot`, `Impulse`, `Swing`, `Anticipate`, `Natural`, `Slow Down`, `AE In/Smooth/Out`. Back, Bounce, Elastic and the Overshoots land each unit past its rest and settle (transforms follow the overshoot, colours clamp). `Anticipate` is an exit: the unit winds back the other way (to −0.38 of the move at 58 %), then snaps through in the last frames. `Custom` plays `bezier`. |
224
+ | `curve` | the app's easing names (`Ease Out`) | Each unit's own easing: `Linear`, `Ease In/Out`, `Smooth`, `Smooth Glide`, `Glide Easing Stop`, `Sine/Expo/Back In/Out/Smooth`, `Cubic In/Out`, `Power`, `Punch`, `Apple Out`, `Bounce Out`, `Elastic`, `Overshoot`, `Smooth Overshoot`, `Impulse`, `Swing`, `Anticipate`, `Natural`, `Slow Down`, `AE In (arrive)`, `AE Smooth`, `AE Out (leave)` (a retired name such as `Power2 Smooth` loads as its replacement). Back, Bounce, Elastic and the Overshoots land each unit past its rest and settle (transforms follow the overshoot, colours clamp). `Anticipate` is an exit: the unit winds back the other way (to −0.38 of the move at 58 %), then snaps through in the last frames. `Custom` plays `bezier`. |
225
225
  | `bezier` | `[x1, y1, x2, y2]` or null (null) | The `Custom` curve's handles, the same shape as a keyframe's `bezier` (x 0…1, y −3…4; past 0…1 = overshoot). Write it together with `"curve": "Custom"`: a `Custom` without it plays `Ease Out`, and any named `curve` drops it. |
226
226
  | `hold` | 0…600 s (0) | `active`: how long a unit stays lit; 0 = until the next unit's turn (voice: until the next word starts). |
227
227
  | `fadeOut` | 0…20 s (0) | `active`: fade back down after `hold`. |
@@ -297,16 +297,16 @@ Worked examples (spread onto a text item; durations assume a 4 s layer):
297
297
 
298
298
  // Fade Up (Time): words rise in 0.06 s apart, and leave upward at the end.
299
299
  "textAnimators": [
300
- { "aid": "a1", "name": "In", "range": { "drive": "time", "animate": "in", "basedOn": "words", "stagger": 0.06, "duration": 0.8, "curve": "Power2 Smooth" },
300
+ { "aid": "a1", "name": "In", "range": { "drive": "time", "animate": "in", "basedOn": "words", "stagger": 0.06, "duration": 0.8, "curve": "Smooth" },
301
301
  "effects": [ { "pid": "p1", "type": "position", "x": 0, "y": 50 }, { "pid": "p2", "type": "opacity", "opacity": 0 } ] },
302
- { "aid": "a2", "name": "Out", "range": { "drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.06, "duration": 0.7, "curve": "Power2 Smooth" },
302
+ { "aid": "a2", "name": "Out", "range": { "drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.06, "duration": 0.7, "curve": "Smooth" },
303
303
  "effects": [ { "pid": "p3", "type": "position", "x": 0, "y": -50 }, { "pid": "p4", "type": "opacity", "opacity": 0 } ] }]
304
304
 
305
305
  // Energetic in and out (Swing Pop and 20 more, each two Time animators): video-text-kinetic.md.
306
306
  // Every other app preset (typing reveals, text on shapes, highlights, loops, decoders, voice): video-text-presets.md.
307
307
 
308
308
  // Caption karaoke on the voice (needs textWordTimings): a box sits under each word while it is spoken.
309
- "textAnimators": [{ "aid": "a1", "name": "Spoken box", "range": { "drive": "voice", "animate": "active", "basedOn": "words", "duration": 0.08, "curve": "Power2 Out", "fadeOut": 0.08 },
309
+ "textAnimators": [{ "aid": "a1", "name": "Spoken box", "range": { "drive": "voice", "animate": "active", "basedOn": "words", "duration": 0.08, "curve": "Ease Out", "fadeOut": 0.08 },
310
310
  "effects": [ { "pid": "p1", "type": "box", "color": "#33efab", "padX": 12, "padY": 4, "radius": 14, "grow": false }, { "pid": "p2", "type": "fill", "color": "#0b0d0c" } ] }]
311
311
 
312
312
  // Letters typed at speaking pace behind a caret (captions).
@@ -359,9 +359,9 @@ PUT /api/moodboards/{board_id}/video-projects/{project_id}
359
359
  "textFontWeight": 800, "textAlign": "center",
360
360
  "panX": 0, "panY": 35,
361
361
  "textAnimators": [
362
- { "aid": "a1", "name": "In", "range": { "drive": "time", "animate": "in", "basedOn": "words", "stagger": 0.06, "duration": 0.8, "curve": "Power2 Smooth" },
362
+ { "aid": "a1", "name": "In", "range": { "drive": "time", "animate": "in", "basedOn": "words", "stagger": 0.06, "duration": 0.8, "curve": "Smooth" },
363
363
  "effects": [ { "pid": "p1", "type": "position", "x": 0, "y": 50 }, { "pid": "p2", "type": "opacity", "opacity": 0 } ] },
364
- { "aid": "a2", "name": "Out", "range": { "drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.06, "duration": 0.7, "curve": "Power2 Smooth" },
364
+ { "aid": "a2", "name": "Out", "range": { "drive": "time", "animate": "out", "basedOn": "words", "stagger": 0.06, "duration": 0.7, "curve": "Smooth" },
365
365
  "effects": [ { "pid": "p3", "type": "position", "x": 0, "y": -50 }, { "pid": "p4", "type": "opacity", "opacity": 0 } ] }
366
366
  ]
367
367
  }