greenlight-motion 0.3.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,23 +10,46 @@
10
10
 
11
11
  ## Contents
12
12
 
13
- - [Shape layers (`mediaType: "shape"`)](video-shapes.md#shape-layers-mediatype-shape)
14
- - [Rounded rectangles — buttons, pills, cards](video-shapes.md#rounded-rectangles--buttons-pills-cards)
13
+ - [Shape layers (`mediaType: "shape"`)](#shape-layers-mediatype-shape)
14
+ - [Sizing (`shapeSizeUnit`)](#sizing-shapesizeunit)
15
+ - [Rounded rectangles — buttons, pills, cards](#rounded-rectangles--buttons-pills-cards)
16
+ - [Fill, opacity and framing on shapes](#fill-opacity-and-framing-on-shapes)
17
+ - [Editable paths (`shapeType: "path"`, `shapePath`)](#editable-paths-shapetype-path-shapepath)
18
+ - [Writing the handles](#writing-the-handles) — the tangent rule for every path in the app
19
+ - [Lines and strokes](#lines-and-strokes)
20
+ - [Shape background (`shapeBackground`)](#shape-background-shapebackground)
21
+ - [Clipping masks (`shapeClipMask` / `textClipMask` / `clipMaskInvert`)](#clipping-masks-shapeclipmask--textclipmask--clipmaskinvert)
22
+ - [Track matte (`trackMatte`)](#track-matte-trackmatte)
23
+ - [Card framing on every visual layer](#card-framing-on-every-visual-layer) — rounded corners, borders and shadows on video, image, text and sequences
24
+ - [Backdrop blur and glass](#backdrop-blur-and-glass)
25
+ - [Traced vectors (`shapeType: "vector"`)](#traced-vectors-shapetype-vector)
15
26
 
16
27
  ### Shape layers (`mediaType: "shape"`)
17
28
 
18
- A solid-color rectangle / ellipse / arrow / triangle — or a custom SVG silhouette — with the full photo-layer option set (transforms, filters, keyframes, layer transitions, blend modes). Like text, the clip is synthetic (no `url`) and the LOOK lives on the timeline item:
29
+ A solid-colour rectangle / ellipse / arrow / triangle — or a custom SVG silhouette — with the full photo-layer option set (transforms, filters, keyframes, layer transitions, blend modes). Like text, the clip is synthetic (no `url`) and the LOOK lives on the timeline item:
19
30
 
20
- ```json
31
+ ```jsonc
21
32
  // clip
22
33
  { "id": "shp1", "source": "LOCAL", "mediaType": "shape", "url": "", "proxyUrl": null, "name": "Shape", "duration": 5 }
23
34
  // item fields (on the timeline item referencing shp1; defaults shown)
24
- { "shapeType": "rectangle" | "ellipse" | "circle" | "arrow" | "triangle" | "path" | "line" | "custom", "shapeFillColor": "#8b5cf6", "shapeSizeUnit": "pct" | "px", "shapeWidthPct": 40, "shapeHeightPct": 40, "shapeWidthPx": null, "shapeHeightPx": null, "shapeRadiusPct": 20, "shapeRadiusPx": null, "shapeSvgUrl": null, "shapePath": null, "shapeClipMask": false, "clipMaskInvert": false }
35
+ { "shapeType": "rectangle", "shapeFillColor": "#8b5cf6", "shapeSizeUnit": "pct", "shapeWidthPct": 40, "shapeHeightPct": 40, "shapeWidthPx": null, "shapeHeightPx": null, "shapeRadiusPct": 20, "shapeRadiusPx": null, "shapeSvgUrl": null, "shapePath": null, "shapeClipMask": false, "clipMaskInvert": false }
25
36
  ```
26
37
 
27
- `shapeType` is one of `rectangle` | `ellipse` | `circle` | `arrow` | `triangle` | `path` | `line` | `custom`. Triangles take `shapeTriDirection` (`up`|`down`|`left`|`right`, default `up`) and `shapeTriApexPct` (0–100, default 50 — where the tip sits along the opposite edge; 0/100 gives a right triangle). Arrows take `shapeArrowHeadPct` (head length as % of width, 5–90, default 45) and `shapeArrowShaftPct` (shaft thickness as % of height, 2–100, default 42). `custom` renders a user-provided SVG silhouette: set `shapeSvgUrl` to an SVG **data URI** (`data:image/svg+xml;base64,…` or URL-encoded) — the SVG's alpha is the shape and it is recolored with `shapeFillColor`, so it travels inside the project JSON with no media file. Stills and exports load every `shapeSvgUrl` in the project before the first frame, so an icon whose clip starts mid-timeline appears on its first frame (the live preview loads it on first display). `shapeWidthPct`/`shapeHeightPct` are % of the frame and may exceed 100; the pixel size fields may likewise exceed the composition dimensions. The size is keyframable in the unit the shape uses: tracks `shapeWidthPx`/`shapeHeightPx` or `shapeWidthPct`/`shapeHeightPct` (a circle: `shapeRadiusPx` or `shapeRadiusPct`), each overriding the field of the same name — a track for the unit not in use does nothing.
38
+ `shapeType` is one of `rectangle` | `ellipse` | `circle` | `arrow` | `triangle` | `path` | `line` | `custom` | `vector`; an unknown value loads as `rectangle`.
39
+
40
+ - **Triangles** take `shapeTriDirection` (`up`|`down`|`left`|`right`, default `up`) and `shapeTriApexPct` (0–100, default 50 — where the tip sits along the opposite edge; 0/100 gives a right triangle).
41
+ - **Arrows** take `shapeArrowHeadPct` (head length as % of width, 5–90, default 45) and `shapeArrowShaftPct` (shaft thickness as % of height, 2–100, default 42).
42
+ - **`custom`** renders a user-provided SVG silhouette: set `shapeSvgUrl` to an SVG **data URI** (`data:image/svg+xml;base64,…` or URL-encoded) — the SVG's alpha is the shape and it is recoloured with `shapeFillColor`, so it travels inside the project JSON with no media file. Stills and exports load every `shapeSvgUrl` in the project before the first frame, so an icon whose clip starts mid-timeline appears on its first frame (the live preview loads it on first display).
43
+ - **`vector`** is a traced image: see [Traced vectors](#traced-vectors-shapetype-vector).
44
+ - **`path`** and **`line`** are editable bezier outlines: see [Editable paths](#editable-paths-shapetype-path-shapepath) and [Lines and strokes](#lines-and-strokes).
45
+
46
+ The size is keyframable in the unit the shape uses: tracks `shapeWidthPx`/`shapeHeightPx` or `shapeWidthPct`/`shapeHeightPct` (a circle: `shapeRadiusPx` or `shapeRadiusPct`), each overriding the field of the same name — a track for the unit not in use does nothing. Shapes render in `/render-onserver` (not the fast `/render`). Put shapes on their own `unsnapped` channel above the footage, like captions.
47
+
48
+ #### Sizing (`shapeSizeUnit`)
28
49
 
29
- **Sizing (`shapeSizeUnit`).** `"pct"` (the default) reads `shapeWidthPct` as % of the composition **width** and `shapeHeightPct` as % of its **height** — two different bases, so equal percentages are only square on a square composition: `40`/`40` on 1920×1080 is a 768×432 oval. `"px"` reads `shapeWidthPx`/`shapeHeightPx` as absolute composition pixels instead (a `null` px value falls back to the %). **To draw a circle, size it in px, never in matching percentages**: either `shapeType: "circle"`, which takes a single radius (`shapeRadiusPx` with `shapeSizeUnit: "px"`; its `shapeRadiusPct` fallback is % of the *shorter* composition side, default 20, so it stays round but changes size with the aspect ratio), or an `ellipse` with `shapeSizeUnit: "px"` and equal `shapeWidthPx`/`shapeHeightPx`. The same applies to any shape that must keep its proportions across compositions (a square, a 1:1 badge): use px.
50
+ `"pct"` (the default) reads `shapeWidthPct` as % of the composition **width** and `shapeHeightPct` as % of its **height** — two different bases, so equal percentages are only square on a square composition: `40`/`40` on 1920×1080 is a 768×432 oval. Both may exceed 100. `"px"` reads `shapeWidthPx`/`shapeHeightPx` as absolute composition pixels instead (a `null` px value falls back to the %), and may exceed the composition size.
51
+
52
+ **To draw a circle, size it in px, never in matching percentages**: either `shapeType: "circle"`, which takes a single radius (`shapeRadiusPx` with `shapeSizeUnit: "px"`; its `shapeRadiusPct` fallback is % of the *shorter* composition side, default 20, so it stays round but changes size with the aspect ratio), or an `ellipse` with `shapeSizeUnit: "px"` and equal `shapeWidthPx`/`shapeHeightPx`. Do the same for any shape that must keep its proportions across compositions (a square, a 1:1 badge).
30
53
 
31
54
  #### Rounded rectangles — buttons, pills, cards
32
55
 
@@ -34,7 +57,9 @@ For anything with rounded corners — a button, pill, badge, tag, card, chat bub
34
57
 
35
58
  - `borderRadiusUnit`: `"px"` (default; composition pixels — a fixed corner) or `"pct"` (% of the shape's **shorter** side, 0–50). `50` is a full pill/capsule at any aspect ratio. Any radius larger than half the shorter side clamps there, so `borderRadius: 9999` in px is also a pill.
36
59
  - `borderRadius` is the radius for all four corners (default 0 = square). For different corners set `borderRadiusLinked: false` and `borderRadiusTL` / `borderRadiusTR` / `borderRadiusBR` / `borderRadiusBL`, in the same unit (a corner left out falls back to `borderRadius`) — e.g. a tab with only its top corners rounded.
37
- - Animate it with the `borderRadius` property-keyframe track (`borderRadiusTL` … `borderRadiusBL` when unlinked). Values are in the item's `borderRadiusUnit`; `"0"` is square, so `0 → 50` in `pct` morphs a square box into a pill.
60
+ - **Animate it** with the `borderRadius` property-keyframe track (`borderRadiusTL` … `borderRadiusBL` when unlinked). Values are in the item's `borderRadiusUnit`; `"0"` is square, so `0 → 50` in `pct` morphs a square box into a pill. Changing `borderRadiusUnit` through the API does not convert existing keys, so rewrite them in the new unit.
61
+ - When the layer itself must scale (a tile that grows to fill the frame, a card that punches in on a `scale` track), set `borderRadiusScaleLock: true`. Every radius is then divided by the layer's scale (`zoom`, the `scale`/`scaleX`/`scaleY` tracks and its parents), so the corner keeps its size on screen instead of growing with the tile. Without it (the default) the corner scales with the layer.
62
+ - Only `rectangle` shapes take a `pct` radius. Every other layer (video, image, text, sequence) takes the radius in px, and its `borderRadius` tracks too (see [Card framing](#card-framing-on-every-visual-layer)); `shapeRadiusPct`/`shapeRadiusPx` are a `circle`'s size, not a corner amount.
38
63
  - `borderWidth`/`borderColor`/`borderStyle` stroke the rounded outline, and `shadows` and `backdropBlur` follow it.
39
64
 
40
65
  A pill button — the label is a text layer on the channel above, at the same `panX`/`panY`:
@@ -50,24 +75,25 @@ A pill button — the label is a text layer on the channel above, at the same `p
50
75
 
51
76
  A 12 px-corner card is `"borderRadius": 12` with no unit field.
52
77
 
53
- **Shape fill opacity comes from the alpha of `shapeFillColor`** — use an rgba/8-digit-hex value (e.g. `rgba(139,92,246,0.5)` or `#8b5cf680`) for a semi-transparent fill; there is no separate fill-opacity field. Position/scale/rotation come from the standard item fields (`panX`, `panY`, `zoom`, `rotateZ`, keyframes…). For whole-layer fade (any layer type, not just shapes) use the standard item `opacity` (0–100). On shape layers the framing fields follow the **shape outline**, not the bounding box: `borderWidth`/`borderColor`/`borderStyle` stroke the path (all four styles; not available on `custom` SVG shapes), `borderRadius` (incl. per-corner, px or `borderRadiusUnit: "pct"` — see [Rounded rectangles](#rounded-rectangles--buttons-pills-cards)) rounds `rectangle` shapes only (`shapeRadiusPct`/`shapeRadiusPx` are the `circle` radius, not a corner amount), and drop `shadows` hug the silhouette (shadow `spread` and `inset` are ignored for shapes). Shapes render in `/render-onserver` (not the fast `/render`). Put shapes on their own `unsnapped` channel above the footage, like captions.
78
+ #### Fill, opacity and framing on shapes
54
79
 
55
- **Editable paths (`shapeType: "path"`, `shapePath`).** A freeform bezier outline instead of a parametric one. `shapePath` is `{ "points": [ … ], "closed": true }`; each point is `{ "x", "y", "hIn": {"x","y"}, "hOut": {"x","y"}, "manual": true }`. Coordinates are **fractions of the shape's own box, centred on it** — `-0.5` is the left/top edge and `0.5` the right/bottom — so `shapeWidthPct`/`shapeHeightPct` still size the whole path (points may go outside ±0.5; the render surface grows to fit). `hIn`/`hOut` are the cubic-bezier control handles stored as **offsets from their own anchor**, not absolute control points — the segment A→B is the cubic `A → A+A.hOut → B+B.hIn → B`.
80
+ - **Fill opacity comes from the alpha of `shapeFillColor`** — use an rgba/8-digit-hex value (e.g. `rgba(139,92,246,0.5)` or `#8b5cf680`) for a semi-transparent fill; there is no separate fill-opacity field.
81
+ - Position/scale/rotation come from the standard item fields (`panX`, `panY`, `zoom`, `rotateZ`, keyframes…). For a whole-layer fade use the standard item `opacity` (0–100; its keyframe track is 0–1).
82
+ - On shape layers the framing fields follow the **shape outline**, not the bounding box: `borderWidth`/`borderColor`/`borderStyle` stroke the outline (all four styles; not on `custom`/`vector` SVG shapes or stroked paths and lines, which use the [line stroke](#lines-and-strokes) instead), and drop `shadows` hug the silhouette (shadow `spread` and `inset` are ignored for shapes).
56
83
 
57
- ⚠️ **Handles are not optional.** A missing or zero-length handle is a hard **corner**, and nothing fills one in for you: the auto-tangent pass runs only when a human adds or drags a point in the editor, while load, save, preview, export and `/render-onserver` all draw the stored handles verbatim. An outline written as bare `x`/`y` points renders as a straight-line **polygon**, not the smooth shape it looks like on paper. Set `manual: true` on every anchor too, or the next point a user adds in the UI re-smooths the whole outline out from under the design. The tangents to write:
84
+ #### Editable paths (`shapeType: "path"`, `shapePath`)
58
85
 
59
- - **Smooth anchor** — the editor's own Catmull-Rom tangent: `hOut = (P[next] − P[prev]) / 6`, `hIn = −hOut`. On a `closed` path the neighbours wrap; on an open one the first anchor uses `hOut = (P[1] − P[0]) / 3` and the last `hIn = −(P[last] − P[last−1]) / 3`.
60
- - **Corner** — `hIn` and `hOut` both `{ "x": 0, "y": 0 }`.
61
- - **Cusp with curvature on both sides** — break the mirror: give `hIn` and `hOut` directions that are not opposite.
62
- - **Circular arc** — a quarter-arc wants handles of `0.5523 × radius` laid **perpendicular** to the anchor's radius, following the winding: `0.2761` in the ±0.5 box (that is the Circle preset). Longer handles bulge the arc (Squircle uses 1.34×), shorter ones flatten it.
86
+ A freeform bezier outline instead of a parametric one. `shapePath` is `{ "points": [ … ], "closed": true }`; each point is `{ "x", "y", "hIn": {"x","y"}, "hOut": {"x","y"}, "manual": true }`. Coordinates are **fractions of the shape's own box, centred on it** — `-0.5` is the left/top edge and `0.5` the right/bottom (+y down) — so `shapeWidthPct`/`shapeHeightPct` still size the whole path (points may go outside ±0.5; the render surface grows to fit). Anchors **and** handles are clamped to **±8** box units on load. Write the handles as described in [Writing the handles](#writing-the-handles).
63
87
 
64
- Anchors **and** handles are clamped to **±8** box units on load. `closed: false` draws the path as a **stroked line** using `borderWidth`/`borderColor`/`borderStyle` (falling back to 4px + `shapeFillColor` when the border width is 0) instead of a filled shape — an open path cannot be a clipping mask. Ready-made outlines come from `GET /api/video-editor/capabilities` → **`paths`**: `paths.shapePresets` holds 11 closed shapes (Circle, Rectangle, Rounded, Triangle, Diamond, Pentagon, Hexagon, Star, Burst, Blob, Squircle) and `paths.linePresets` 6 open ones (Straight, Arc, S-curve, Wave, Zigzag, Corner), each as a complete point list with handles already resolved and `manual: true` set — paste one and it renders exactly as published. `paths.anchor` / `paths.spaces` / `paths.tangents` restate the contract above for every path kind. Take the points from there rather than approximating a shape by hand (the generator reads `SHAPE_PATH_PRESETS`/`LINE_PATH_PRESETS` in `frontend/src/components/videoEditor/shapePath.js`); on a keyframed outline, write the resampled points into a key instead of the base field.
88
+ `closed: false` (or `shapePathRenderMode: "stroke"`) draws the path as a **stroked line** with the [line stroke fields](#lines-and-strokes) instead of a filled shape; an open path cannot be a clipping mask.
65
89
 
66
- **Reusing another curve as an outline.** A motion path or camera path can become a shape outline: world-pixel curves are fitted into **this** shape's box with a single scale for both axes (keeping proportions rather than stretching to the box), centred on it, and flattened — `z` is dropped, since a shape draws its XY footprint. `worldPointsToShapePoints` in `frontend/src/components/videoEditor/pathCopy.js` is the conversion.
90
+ **Ready-made outlines** come from `GET /api/video-editor/capabilities` → **`paths`**: `paths.shapePresets` holds 11 closed shapes (Circle, Rectangle, Rounded, Triangle, Diamond, Pentagon, Hexagon, Star, Burst, Blob, Squircle) and `paths.linePresets` 6 open ones (Straight, Arc, S-curve, Wave, Zigzag, Corner), each as a complete point list with handles already resolved and `manual: true` set — paste one and it renders exactly as published. Take the points from there rather than approximating a shape by hand; on a keyframed outline, write the resampled points into a key instead of the base field.
67
91
 
68
- The outline is keyframable as a whole under the `shapePath` **property-keyframe track** — one of only two track kinds whose `value` is not a number or colour but a JSON array of points (the other is a layer mask's `msk:<mid>:path`; see `video-effects.md`). Every key must hold the **same number of points**, since they morph one-to-one:
92
+ **Reusing another curve as an outline.** A motion path or camera path can become a shape outline: world-pixel curves are fitted into **this** shape's box with a single scale for both axes (keeping proportions rather than stretching to the box), centred on it, and flattened — `z` is dropped, since a shape draws its XY footprint, and y flips (world +y is up, the box's +y is down).
69
93
 
70
- ```json
94
+ The outline is keyframable as a whole under the `shapePath` **property-keyframe track** — one of the track kinds whose `value` is a JSON array of points rather than a number or colour (the others are a layer mask's `msk:<mid>:path`, see [Layer Mask entry](video-effects.md#layer-mask-entry), and `motionPathPoints`, see [video-paths.md](video-paths.md#animated-motion-path-motionpathpoints)). Every key must hold the **same number of points**, since they morph one-to-one:
95
+
96
+ ```jsonc
71
97
  "propertyKeyframes": { "shapePath": [
72
98
  { "position": 0, "value": "[{\"x\":-0.5,\"y\":-0.5,\"hIn\":{\"x\":0,\"y\":0},\"hOut\":{\"x\":0,\"y\":0},\"manual\":true}, …]", "easing": "Smooth" },
73
99
  { "position": 100, "value": "[…same length…]", "easing": "Smooth" }
@@ -76,7 +102,20 @@ The outline is keyframable as a whole under the `shapePath` **property-keyframe
76
102
 
77
103
  The track is only kept on load when the item's `shapeType` is `path` or `line`.
78
104
 
79
- **Lines and strokes (`shapeType: "line"`, or `"path"` with `closed: false`).** A `line` is an editable path that renders as a **stroke** rather than a fill (its `closed` flag is ignored — a line is always open). Stroke fields, all on the item: `lineWidth` (px, default 8), `lineCap` (`butt`|`round`|`square`, default `round`), `lineJoin` (`round`|`miter`|`bevel`, default `round`), `lineDash` (`solid`|`dashed`|`dotted`|`custom`, default `solid`). The stroke is painted with the shape's own fill, so `shapeFillColor` **and** a full `shapeBackground` (gradient / shader / pattern / image) both work on a line. The card `borderWidth`/`borderStyle` do **not** apply — the stroke is the outline.
105
+ #### Writing the handles
106
+
107
+ One rule covers every bezier path in the app: `shapePath`, line outlines and Layer Mask paths (unit box, `x`/`y`), and `motionPath`, `cameraPath` and `motionPathPoints` keys (world pixels, `x`/`y`/`z` — every handle carries `z` too). `GET /api/video-editor/capabilities` → `paths.anchor` / `paths.spaces` / `paths.tangents` restate it.
108
+
109
+ `hIn`/`hOut` are the cubic-bezier control handles stored as **offsets from their own anchor**, not absolute control points — the segment A→B is the cubic `A → A+A.hOut → B+B.hIn → B`. **Write both handles on every anchor.** A missing or zero-length handle is a hard **corner**, and nothing fills one in: the auto-tangent pass runs only on a hand edit in the editor, while load, save, preview, export and `/render-onserver` draw the stored handles verbatim. An outline written as bare points renders as a straight-line **polygon**, not the smooth shape it looks like on paper. Set **`manual: true` on every anchor** too, or the next hand edit in the editor re-smooths the whole outline out from under the design.
110
+
111
+ - **Smooth anchor** — the editor's own Catmull-Rom tangent, componentwise: `hOut = (P[next] − P[prev]) / 6`, `hIn = −hOut`. On a `closed` path the neighbours wrap; on an open one the first anchor uses `hOut = (P[1] − P[0]) / 3` and the last `hIn = −(P[last] − P[last−1]) / 3`.
112
+ - **Corner** — `hIn` and `hOut` both `{ "x": 0, "y": 0 }` (plus `"z": 0` in world space).
113
+ - **Cusp with curvature on both sides** — break the mirror: give `hIn` and `hOut` directions that are not opposite.
114
+ - **Circular arc** — a quarter-arc wants handles of `0.5523 × radius` laid **perpendicular** to the anchor's radius, following the winding: `0.2761` in the ±0.5 box (that is the Circle preset). Longer handles bulge the arc (Squircle uses 1.34×), shorter ones flatten it.
115
+
116
+ #### Lines and strokes
117
+
118
+ A `line` is an editable path that renders as a **stroke** rather than a fill (its `closed` flag is ignored — a line is always open). A `path` is stroked the same way when `closed: false` or when `shapePathRenderMode` is `"stroke"` (default `"fill"`) — what you want for a signature, a route line or any figure where a fill would close the curve. Stroke fields, all on the item: `lineWidth` (px, 0–400, default 8), `lineCap` (`butt`|`round`|`square`, default `round`), `lineJoin` (`round`|`miter`|`bevel`, default `round`), `lineDash` (`solid`|`dashed`|`dotted`|`custom`, default `solid`). The stroke is painted with the shape's own fill, so `shapeFillColor` **and** a full `shapeBackground` (gradient / shader / pattern / image) both work on a line. The card `borderWidth`/`borderStyle` do **not** apply — the stroke is the outline.
80
119
 
81
120
  **Trim Paths (`lineTrimStart` / `lineTrimEnd` / `lineTrimOffset`).** Draw only part of the outline, measured by arc length: `lineTrimStart` and `lineTrimEnd` are 0–100 (default 0 and 100), `lineTrimOffset` is −100…100 (default 0) and slides the span along the path, wrapping on a closed one. Keyframing `lineTrimEnd` 0 → 100 is the "write the line on" move. Trim applies to any stroked shape.
82
121
 
@@ -122,28 +161,52 @@ A tapered arrow that writes itself on:
122
161
  - Flat fields: `lineWidth`, `lineTrimStart`, `lineTrimEnd`, `lineTrimOffset`, `lineDashOffset`, `lineDashSpeed`, `lineOutlineWidth`, `lineGhost`, and the custom dash pattern as `lineDashLength1`, `lineDashGap1`, `lineDashLength2`, `lineDashGap2`.
123
162
  - `lineStroke` numbers: a track named `line` + card key + field, both capitalised — `lineBrushNibAngle`, `lineWarpZigzagRidges`, `lineCopiesRotate`, `lineStampsSpacing`, `linePaintRepeat` … — except these, which keep their own names: `lineTaperStartLength`, `lineTaperEndLength`, `lineTaperStartWidth`, `lineTaperEndWidth`, `lineProfileOffset`, `lineWidthWaveAmount`, `lineWidthWavePhase`, `lineWarpOffset`, `lineZigzagSize`, `lineWiggleSize`, `lineWarpWaveAmplitude`, `lineCopySpacing`, `lineEndsSize`, `lineStampOffset`, `lineGradientOffset`, `lineFadeIn`, `lineFadeOut`.
124
163
  - `lineStroke` colours (key `value`s are CSS colours, interpolated as colours): `linePaintColor1`–`3` and `lineCopiesColor1`–`3` (the entries of `colors`), `linePaintCoreColor`, and `lineStampsColor` (only recolours stamps that have their own `color`).
164
+
125
165
  A track overrides its field only while that card is on the layer; values use the field's units and are clamped to its range.
126
166
 
127
167
  Running Glow on a stroked shape should set `glowTraceThin: 1` and a `glowEdgeWidth` near the line's own thickness (in @1080 px) — the default filled-shape tracing finds no contour on a thin stroke and the effect is invisible.
128
168
 
129
- **Shape background (`shapeBackground`).** Beyond the flat `shapeFillColor`, a shape can take a full **background** via `shapeBackground` — the same `backgroundConfig` object as the project background (solid, gradient, GLSL shader, pattern, or image; full schema, preset ids and the shader rules are in the **Backgrounds (project & shape)** section above). It fills the shape's silhouette; when `shapeBackground` is `null` (the default) the shape falls back to the flat `shapeFillColor`. Shader and animated-gradient shape backgrounds render only in the Video Editor.
169
+ #### Shape background (`shapeBackground`)
130
170
 
131
- **Clipping masks (`shapeClipMask` / `textClipMask` / `clipMaskInvert`).** A shape can act as a **clipping mask**: set `shapeClipMask: true` and the layers below the shape's channel show only where the shape has coverage (the fill itself isn't drawn; the fill's alpha sets mask strength, and the shape's transforms/keyframes move the mask). **Text layers** take the same treatment with `textClipMask: true` — the glyph coverage is the matte, so footage plays inside the letters, and typography plus the split/stagger animation fields animate it. While a text layer masks, its own text shadow (`textShadowX/Y/Blur/Spread`) and caption bar (`textBgEnabled`) are ignored, as is card framing (`borderWidth`, `shadows`) — that alpha would leak into the matte; use the `blur` filter to feather instead. Either kind accepts `clipMaskInvert: true` to flip the matte (knockout: the layers below are hidden inside the shape/letters and shown everywhere else). A mask affects **all** channels below it in the same stack, masks stack, and they composite both at the top level and inside a sequence's inner timeline. A frame with an active mask skips the global Depth of Field.
171
+ Beyond the flat `shapeFillColor`, a shape can take a full **background** via `shapeBackground` — the same `backgroundConfig` object as the project background (solid, gradient, GLSL shader, pattern, or image; the schema, preset ids and shader rules are in [video-backgrounds.md](video-backgrounds.md)). It fills the shape's silhouette; when `shapeBackground` is `null` (the default) the shape falls back to the flat `shapeFillColor`. Shader and animated-gradient shape backgrounds render only in the Video Editor.
132
172
 
133
- **Track matte (`trackMatte`).** The After Effects analog, distinct from clipping masks: it scopes to **one consumer layer** instead of the whole stack below, and supports luma. Set on the consumer item: `trackMatte: { sourceId: "<other item id>", mode: "alpha" | "alphaInverted" | "luma" | "lumaInverted" }` — the consumer then shows only where the source layer has alpha coverage (`alpha`) or brightness (`luma`; computed premultiplied, so transparent source areas contribute nothing), inverted variants flip it. The **source layer is hidden from output** while at least one active consumer references it; its transforms/keyframes still animate the matte. Rules: the source must be another **visual** item (not audio/camera/adjustment) at the **same editing level**; the reference is by item id, not stack position; several consumers may share one source; a mutual pair (A mattes with B while B mattes with A) is auto-broken on save; if the source clip isn't under the playhead the matte is EMPTY — `alpha`/`luma` hide the consumer, inverted modes show it untouched. The matte clips the consumer's **finished** look (after its filters, glow, overlay effects and motion blur), matching AE. The **source is read finished too**: its own `effects` chain, `layerEffects`, rig warp and motion blur shape the matte, so an `extract` (histogram key) or `alphaChoker` on the source layer is the normal way to author a matte from footage rather than from a silhouette. Works at the top level and inside a sequence's inner timeline alike (inside a sequence only the layer's `effects` chain shapes the matte — glow, motion blur and the other Layer Effects are top-level stages); grids strip it on grouping.
173
+ #### Clipping masks (`shapeClipMask` / `textClipMask` / `clipMaskInvert`)
134
174
 
135
- Every visual layer (video/image/shape/text/sequence) additionally supports **card framing** item fields, all in canvas px:
175
+ A shape can act as a **clipping mask**: set `shapeClipMask: true` and the layers below the shape's channel show only where the shape has coverage (the fill itself isn't drawn; the fill's alpha sets mask strength, and the shape's transforms/keyframes move the mask). **Text layers** take the same treatment with `textClipMask: true` — the glyph coverage is the matte, so footage plays inside the letters, and typography plus the split/stagger animation fields animate it. While a text layer masks, its own text shadow (`textShadowX/Y/Blur/Spread`) and caption bar (`textBgEnabled`) are ignored, as is card framing (`borderWidth`, `shadows`) — that alpha would leak into the matte; use the `blur` filter to feather instead. Either kind accepts `clipMaskInvert: true` to flip the matte (knockout: the layers below are hidden inside the shape/letters and shown everywhere else). A mask affects **all** channels below it in the same stack, masks stack, and they composite both at the top level and inside a sequence's inner timeline. A frame with an active mask skips the global Depth of Field.
136
176
 
137
- - **Rounded corners** — `borderRadius` (uniform, default 0). For per-corner radii set `borderRadiusLinked: false` and use `borderRadiusTL`, `borderRadiusTR`, `borderRadiusBR`, `borderRadiusBL` (each falls back to `borderRadius`). On `rectangle` shapes the radius can also be a % of the shorter side and is keyframable ([Rounded rectangles](#rounded-rectangles--buttons-pills-cards)); on media layers it is static px.
138
- - **Border** — `borderWidth` + `borderColor` (default 0 / `#ffffff`) + `borderStyle` (`solid` | `dashed` | `dotted` | `double`, default `solid`). `borderWidth` and `borderColor` are also keyframe tracks (`propertyKeyframes.borderWidth` in px, `propertyKeyframes.borderColor` as CSS colours) — a focus ring that grows, an outline that turns red on an error; a track overrides its field for the frame.
139
- - **Shadows** — `shadows`: an array of up to 4 layers, each `{ x, y, blur, spread, color, inset }` (CSS box-shadow semantics; `inset: true` draws an inner shadow; the first entry sits on top). Legacy single-shadow fields (`shadowX`, `shadowY`, `shadowBlur`, `shadowSpread`, `shadowColor`) are still read when `shadows` is absent and migrate on load.
177
+ #### Track matte (`trackMatte`)
140
178
 
141
- - **Backdrop Blur** (shape layers ONLY — matte / frosted glass) — `backdropBlur` (px, 0–200, default 0 = off) blurs whatever is rendered **behind** the shape, inside its outline; the shape's own fill then draws over the blur, so a semi-transparent fill (`shapeFillColor: "rgba(255,255,255,0.25)"`) reads as frosted glass and a tinted one as smoked glass. `backdropSaturation` (%, 0–300, default 100) re-saturates the blurred backdrop (boost it for the iOS/macOS glass look). Both are keyframable as the `backdropBlur` / `backdropSaturation` tracks. The blur covers the shape's full coverage regardless of the fill's alpha (the plate alpha is normalised by the fill's peak alpha × `opacity`), fades with a gradient fill, and is ignored on clip-mask shapes and on every non-shape layer. A fully opaque fill hides the blur entirely — always pair `backdropBlur` with a semi-transparent `shapeFillColor` (the panel thins a solid fill to 35 % when Blur is first turned on and remembers the old paint in `backdropGlassFill`; setting Blur back to 0 or the section reset restores it unless the fill was edited since. The API does neither: set `backdropBlur: 0` AND the fill you want).
179
+ The After Effects analog, distinct from clipping masks: it scopes to **one consumer layer** instead of the whole stack below, and supports luma. Set on the consumer item: `trackMatte: { sourceId: "<other item id>", mode: "alpha" | "alphaInverted" | "luma" | "lumaInverted" }` — the consumer then shows only where the source layer has alpha coverage (`alpha`) or brightness (`luma`; computed premultiplied, so transparent source areas contribute nothing), inverted variants flip it. The **source layer is hidden from output** while at least one active consumer references it; its transforms/keyframes still animate the matte.
142
180
 
143
- - **Glass** (shape layers only: `rectangle`, `ellipse`, `circle`) — the `glass` Layer Effect turns the shape into a pane of real glass that refracts, frosts, tints and shadows everything rendered **behind** it; see [Glass entry](video-effects.md#glass-entry).
181
+ - The source must be another **visual** item (not audio/camera/adjustment) at the **same editing level**; the reference is by item id, not stack position; several consumers may share one source.
182
+ - A mutual pair (A mattes with B while B mattes with A) is auto-broken on save.
183
+ - If the source clip isn't under the playhead the matte is EMPTY — `alpha`/`luma` hide the consumer, inverted modes show it untouched.
184
+ - The matte clips the consumer's **finished** look (after its filters, glow, overlay effects and motion blur), matching AE. The **source is read finished too**: its own `effects` chain, `layerEffects`, rig warp and motion blur shape the matte, so an `extract` (histogram key) or `alphaChoker` on the source layer is the normal way to author a matte from footage rather than from a silhouette.
185
+ - Works at the top level and inside a sequence's inner timeline alike (inside a sequence only the layer's `effects` chain shapes the matte — glow, motion blur and the other Layer Effects are top-level stages); grids strip it on grouping.
186
+
187
+ #### Card framing on every visual layer
188
+
189
+ Every visual layer (video/image/shape/text/sequence) supports **card framing** item fields, all in canvas px:
190
+
191
+ - **Rounded corners** — `borderRadius` (uniform, default 0); for per-corner radii set `borderRadiusLinked: false` and use `borderRadiusTL`, `borderRadiusTR`, `borderRadiusBR`, `borderRadiusBL` (each falls back to `borderRadius`). On media layers the radius is px; only `rectangle` shapes can also take a % of the shorter side ([Rounded rectangles](#rounded-rectangles--buttons-pills-cards)). It is keyframable on every layer: `propertyKeyframes.borderRadius` (the four corner tracks while unlinked) overrides the field for the frame, so a full-frame video can round into a card. The radius is drawn before the layer's scale, so a scaled picture's corners grow with it; `borderRadiusScaleLock: true` divides every radius by the layer's scale and keeps the corner's on-screen size. Border and shadows follow the rounded outline either way. A picture tile that grows 4× with a 24 px corner the whole way:
192
+
193
+ ```json
194
+ { "zoom": 0.25, "borderRadius": 24, "borderRadiusScaleLock": true,
195
+ "propertyKeyframes": { "scale": [
196
+ { "position": 0, "value": "1", "easing": "AE Smooth" },
197
+ { "position": 100, "value": "4", "easing": "AE Smooth" }
198
+ ] } }
199
+ ```
200
+ - **Border** — `borderWidth` + `borderColor` (default 0 / `#ffffff`) + `borderStyle` (`solid` | `dashed` | `dotted` | `double`, default `solid`). `borderWidth` and `borderColor` are also keyframe tracks (`propertyKeyframes.borderWidth` in px, `propertyKeyframes.borderColor` as CSS colours) — a focus ring that grows, an outline that turns red on an error; a track overrides its field for the frame.
201
+ - **Shadows** — `shadows`: an array of up to 4 layers, each `{ x, y, blur, spread, color, inset }` (CSS box-shadow semantics; `inset: true` draws an inner shadow; the first entry sits on top). The single-shadow fields (`shadowX`, `shadowY`, `shadowBlur`, `shadowSpread`, `shadowColor`) are read when `shadows` is absent and are converted on load.
144
202
 
145
203
  These bake into `/render-onserver` like everything else.
146
204
 
147
- **Stroke instead of fill (`shapePathRenderMode`).** On `path` and `custom` shapes, `"fill"` (the default) paints the enclosed area with `shapeFillColor`; `"stroke"` draws the outline only, at `shapeStrokeWidth`, which is what you want for a signature, a route line or any open figure where a fill would close the curve. `line` shapes are always stroked regardless.
205
+ #### Backdrop blur and glass
206
+
207
+ - **Backdrop Blur** (shape layers ONLY — matte / frosted glass) — `backdropBlur` (px, 0–200, default 0 = off) blurs whatever is rendered **behind** the shape, inside its outline; the shape's own fill then draws over the blur, so a semi-transparent fill (`shapeFillColor: "rgba(255,255,255,0.25)"`) reads as frosted glass and a tinted one as smoked glass. `backdropSaturation` (%, 0–300, default 100) re-saturates the blurred backdrop (boost it for the iOS/macOS glass look). Both are keyframable as the `backdropBlur` / `backdropSaturation` tracks. The blur covers the shape's full coverage regardless of the fill's alpha (the plate alpha is normalised by the fill's peak alpha × `opacity`), fades with a gradient fill, and is ignored on clip-mask shapes and on every non-shape layer. A fully opaque fill hides the blur entirely — always pair `backdropBlur` with a semi-transparent `shapeFillColor`. (The editor thins a solid fill to 35 % when `backdropBlur` first goes above 0 and remembers the previous paint in `backdropGlassFill`; setting `backdropBlur` back to 0 there restores it unless the fill was edited since. The API does neither: set `backdropBlur: 0` AND the fill you want.)
208
+ - **Glass** (shape layers only: `rectangle`, `ellipse`, `circle`) — the `glass` Layer Effect turns the shape into a pane of real glass that refracts, frosts, tints and shadows everything rendered **behind** it; see [Glass entry](video-effects.md#glass-entry).
209
+
210
+ #### Traced vectors (`shapeType: "vector"`)
148
211
 
149
- **Traced-vector provenance.** A shape produced by the image → vector tracer keeps where it came from: `shapeVectorSourceUrl` (the bitmap it was traced from), `shapeVectorFileName` (that file's name, ≤240 chars), `shapeVectorPathCount` (how many sub-paths the trace produced) and `shapeVectorTrace` (the tracer settings, so the trace can be re-run at a different threshold). All four are bookkeeping — the drawn artwork is `shapePath` / `shapeSvgUrl` like any other shape — but preserve them when you edit such a layer or the user loses the ability to re-trace.
212
+ The editor's **Image to vector** tracer makes a `vector` shape: the traced artwork is drawn from `shapeSvgUrl` in its own colours (a `custom` SVG is recoloured with `shapeFillColor`; a `vector` one is not), contain-fitted in the shape's box. The layer keeps where it came from: `shapeVectorSourceUrl` (the bitmap it was traced from), `shapeVectorFileName` (that file's name, ≤ 240 chars), `shapeVectorPathCount` (how many sub-paths the trace produced), `shapeVectorTrace` (the tracer settings, so the trace can be re-run at a different threshold) and `shapeVectorEditablePath` (an editable outline: converting the layer to a `path` in the editor uses it, drawn as a stroke). Preserve all five when you edit such a layer, or the user loses the ability to re-trace or convert it.
@@ -10,36 +10,38 @@
10
10
 
11
11
  ## Contents
12
12
 
13
- - [When to use them](video-text-kinetic.md#when-to-use-them)
14
- - [How every recipe works](video-text-kinetic.md#how-every-recipe-works)
15
- - [Writing a recipe onto a layer](video-text-kinetic.md#writing-a-recipe-onto-a-layer)
16
- - [The catalogue](video-text-kinetic.md#the-catalogue) and [Recipes](video-text-kinetic.md#recipes)
13
+ - [When to use them](#when-to-use-them)
14
+ - [How every recipe works](#how-every-recipe-works)
15
+ - [Writing a recipe onto a layer](#writing-a-recipe-onto-a-layer)
16
+ - [The catalogue](#the-catalogue) and [Recipes](#recipes)
17
17
 
18
18
  ## When to use them
19
19
 
20
- **Prefer these 21 recipes whenever text should feel energetic** — a hook, a title card, a promo or social
20
+ **Prefer these 22 recipes whenever text should feel energetic** — a hook, a title card, a promo or social
21
21
  line, a beat-synced word, a call to action, a reveal; anything the user calls punchy, bouncy, poppy, playful,
22
22
  dynamic, kinetic, lively, "with energy" or "make it pop". They are the app's own Kinetic presets: tuned
23
23
  in-and-out motion that a hand-built animator rarely matches, so start from one instead of composing animators
24
24
  from scratch, then adjust.
25
25
 
26
26
  Not for calm or readable-first text: captions under speech, tutorials, long paragraphs, lower thirds. Use a
27
- plain Time fade or rise there (Fade Up in video-text.md) — a swinging paragraph is hard to read.
27
+ plain Time fade or rise there ([Classic Fade Up](video-text-presets.md#classic-fade-up) or
28
+ [Classic Rise (Mask)](video-text-presets.md#classic-rise-mask)) — a swinging paragraph is hard to read.
28
29
 
29
30
  Pick by the text:
30
31
 
31
- - **2–4 short lines** (a stacked title): a *lines* recipe. Swing Pop is the signature one.
32
- - **One line of 3–8 words**: a *words* recipe.
32
+ - **2–4 short stacked lines** (one or a few words each): a *words* recipe; Swing Pop is the signature one.
33
+ - **One line of up to ~5 words** (a hook, a title, a call to action): a *words* recipe. A longer line or a caption
34
+ under speech reads cheap with this much motion; use a calm preset there ([video-design.md](video-design.md#text-animation)).
33
35
  - **One short word or title, up to ~20 letters**: a *letters* recipe.
34
36
 
35
37
  Preferred recipes: when the user has not named one, choose from these first, as long as the recipe fits the text by
36
38
  the rules above: [Swing Pop](#swing-pop), [Pendulum](#pendulum), [Fan Deal](#fan-deal) and [Whip](#whip)
37
- (lines); [Word Swing](#word-swing) (words); [Letter Vortex](#letter-vortex)
39
+ (words; they read well stacked); [Word Swing](#word-swing) (words); [Letter Vortex](#letter-vortex)
38
40
  (letters).
39
41
 
40
42
  ## How every recipe works
41
43
 
42
- Each recipe is **two Time animators** (some add a third, a quick fade) on one text layer, and nothing else:
44
+ Each recipe is exactly **two Time animators** on one text layer, and nothing else:
43
45
 
44
46
  - the **in** animator (`"animate": "in"`): every unit arrives from its effects, `stagger` seconds apart,
45
47
  from the layer's start;
@@ -48,33 +50,36 @@ Each recipe is **two Time animators** (some add a third, a quick fade) on one te
48
50
 
49
51
  There are no keyframes, so a recipe fits any layer length: changing the layer's duration keeps the entrance at
50
52
  the start and the exit at the end. One side takes `(units − 1) × stagger + duration` seconds; keep the layer
51
- longer than the in and out sides together plus the time the text should rest (line recipes: 2.5 s or more;
53
+ longer than the in and out sides together plus the time the text should rest (word recipes on 2–4 lines: 2.5 s or more;
52
54
  letter recipes on long text need more).
53
55
 
54
- `"around": "all"` turns every unit around the centre of the **whole** text, not its own centre, so outer lines
55
- and letters travel on arcs — Swing Pop's look. `textAnimatorPivotX` / `textAnimatorPivotY` (−100…100) move
56
- that point: Pendulum hangs from the top (`textAnimatorPivotY: -100`), Fan Deal fans from the bottom, Door
57
- Swing hinges on the left edge, Domino stands each line on its own bottom-left corner.
56
+ `"around": "all"` turns every unit around the centre of the **whole** text, not its own centre, so outer words
57
+ and letters travel on arcs — Word Swing's and Letter Vortex's look. `textAnimatorPivotX` / `textAnimatorPivotY`
58
+ (−100…100) move each unit's turning point: Pendulum hangs each word from its top edge (`textAnimatorPivotY: -100`),
59
+ Fan Deal fans each word from its bottom edge, Door Swing hinges each word on its left edge, Domino stands each word
60
+ on its bottom-left corner.
58
61
 
59
- The curves do the energy: `Overshoot` lands past rest and settles, `Elastic` and `Bounce Out` wobble or bounce,
60
- `Swing` dips before it springs, and `Anticipate` (the usual exit) winds back the other way, then snaps through
62
+ The curves do the energy: `Overshoot` lands past rest and settles, `Elastic` and `Bounce Out` wobble or bounce, and `Anticipate` (the usual exit) winds back the other way, then snaps through
61
63
  in the last frames.
62
64
 
63
65
  ## Writing a recipe onto a layer
64
66
 
65
- - Copy the recipe's JSON into the text item: its `textAnimators`, plus `textAnimatorPivotX` / `textAnimatorPivotY`
66
- when the recipe has them. Replace the layer's existing `textAnimators`, or append with fresh unique `aid` /
67
- `pid` values.
68
- - **Lengths are for `textFontSize` 100.** Multiply every `position` `x` / `y`, `blur` `amount` and `rotate3d`
69
- `perspective` by `textFontSize / 100`. Angles, scales, opacities and timings stay as written.
67
+ Follow [Writing a recipe onto a layer](video-text-presets.md#writing-a-recipe-onto-a-layer) in text-presets.md (replace or
68
+ add, ids, scaling lengths to the font size). Kinetic recipes have no keyframes, so they fit any layer length as
69
+ written. In short:
70
+ - **Replace**: delete the old `textAnimators` and every `txa:` track, and set both pivots to the recipe's values (0
71
+ when it has none). Recipes reuse ids like `a1`/`p1`, so a leftover track or pivot would drive the new animator.
72
+ - **Lengths are for a 100 px font.** Multiply every `position` `x` / `y` / `z`, `blur` `amount`, `rotate3d`
73
+ `perspective` and the layer's `textAnimatorPerspective` by the layer's font size in px / 100: `textFontSize`, or in pct mode
74
+ `textFontSizePct × videoSize.h / 100`. Angles, scales, opacities and timings stay as written.
70
75
  - Faster or slower: divide every `stagger` and `duration` by the same factor (2 = twice as fast).
71
76
  - Colour, font and size are the layer's own; the recipes only move the text.
72
77
  - To check it, render a still at the middle of the entrance and one in the middle of the layer
73
- (video-rendering.md): mid-entrance the text is moving, mid-layer it rests exactly as typed.
78
+ (rendering.md): mid-entrance the text is moving, mid-layer it rests exactly as typed.
74
79
 
75
80
  ## The catalogue
76
81
 
77
- <!-- KINETIC:BEGIN (generated by tests/text-animators/skill_presets.mjs from presets.js — edit there) -->
82
+ <!-- KINETIC:BEGIN (generated from the app's presets; do not edit by hand) -->
78
83
 
79
84
  | Recipe | What it does | Unit | Turns around | In · Out curve |
80
85
  |---|---|---|---|---|
@@ -82,14 +87,14 @@ in the last frames.
82
87
  | [Word Swing](#word-swing) | Words pop out of the middle of the text in random order and swing round into place; at the end they wind up and pop back | words | centre of the whole text | Overshoot · Anticipate |
83
88
  | [Letter Vortex](#letter-vortex) | Letters spiral out of the centre of the text one by one; at the end they spin back into it | letters | centre of the whole text | Overshoot · Anticipate |
84
89
  | [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
- | [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 |
90
+ | [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 top edge | Elastic · Anticipate |
86
91
  | [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
92
  | [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
93
  | [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
94
  | [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
95
  | [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 |
91
96
  | [Orbit](#orbit) | Words turn in from a third of a turn away and ease into place; at the end they turn off | words | each word's centre | Smooth Overshoot · Anticipate |
92
- | [Fan Deal](#fan-deal) | Words fan up from their bottom edge like cards dealt from a hand, last word first; at the end they fan away | words | each word's centre | Overshoot · Anticipate |
97
+ | [Fan Deal](#fan-deal) | Words fan up from their bottom edge like cards dealt from a hand, last word first; at the end they fan away | words | each word's bottom edge | Overshoot · Anticipate |
93
98
  | [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
99
  | [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
100
  | [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 |
@@ -97,7 +102,9 @@ in the last frames.
97
102
  | [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
103
  | [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
104
  | [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 |
100
- | [Door Swing](#door-swing) | Words swing open in 3D on a hinge at their left edge, past flat and back; at the end they swing shut | words | each word's centre | Overshoot · Anticipate |
105
+ | [Door Swing](#door-swing) | Words swing open in 3D on a hinge at their left edge, past flat and back; at the end they swing shut | words | each word's left edge | Overshoot · Anticipate |
106
+ | [Title Push 3D](#title-push-3d) | Words fly in from depth one by one, overshoot toward the camera and settle as the line glides over to make room; at the end they rush past the camera | words | each word's centre | Back Out · Ease In |
107
+ | [Perspective Type](#perspective-type) | Lines drive in one after another on a plane tilted toward the camera, which then swings flat; at the end they sink back into depth | lines | each line's centre | AE Smooth · Ease In |
101
108
 
102
109
  ## Recipes
103
110
 
@@ -446,4 +453,48 @@ Words swing open in 3D on a hinge at their left edge, past flat and back; at the
446
453
  }
447
454
  ```
448
455
 
456
+ ### Title Push 3D
457
+
458
+ Words fly in from depth one by one, overshoot toward the camera and settle as the line glides over to make room; at the end they rush past the camera.
459
+
460
+ ```json
461
+ {
462
+ "textAnimators": [
463
+ { "aid": "a1", "name": "Fly in",
464
+ "range": {"basedOn": "words", "drive": "time", "animate": "in", "around": "word", "stagger": 0.2, "duration": 0.75, "curve": "Back Out"},
465
+ "effects": [{"pid": "p1", "type": "position", "x": 0, "y": 35, "z": -2600}, {"pid": "p2", "type": "rotate3d", "x": -50, "y": 0, "perspective": 600}, {"pid": "p3", "type": "opacity", "opacity": 0}] },
466
+ { "aid": "a2", "name": "Push",
467
+ "range": {"basedOn": "lines", "drive": "time", "animate": "in", "around": "all", "stagger": 0, "duration": 1.6, "curve": "Sine Out"},
468
+ "effects": [{"pid": "p4", "type": "position", "x": 240, "y": 0}] },
469
+ { "aid": "a3", "name": "Rush out",
470
+ "range": {"basedOn": "words", "drive": "time", "animate": "out", "around": "word", "order": "backward", "stagger": 0.05, "duration": 0.55, "curve": "Ease In"},
471
+ "effects": [{"pid": "p5", "type": "position", "x": 0, "y": 0, "z": 900}, {"pid": "p6", "type": "opacity", "opacity": 0}] }
472
+ ],
473
+ "textAnimatorCamera": "layer",
474
+ "textAnimatorPerspective": 1300
475
+ }
476
+ ```
477
+
478
+ ### Perspective Type
479
+
480
+ Lines drive in one after another on a plane tilted toward the camera, which then swings flat; at the end they sink back into depth.
481
+
482
+ ```json
483
+ {
484
+ "textAnimators": [
485
+ { "aid": "a1", "name": "Lines in",
486
+ "range": {"basedOn": "lines", "drive": "time", "animate": "in", "around": "line", "stagger": 0.5, "duration": 0.5, "curve": "AE Smooth"},
487
+ "effects": [{"pid": "p1", "type": "position", "x": 0, "y": 140}, {"pid": "p2", "type": "scale", "scale": 55}, {"pid": "p3", "type": "opacity", "opacity": 0}] },
488
+ { "aid": "a2", "name": "Tilted plane",
489
+ "range": {"basedOn": "lines", "drive": "time", "animate": "in", "around": "all", "delay": 1.2, "stagger": 0, "duration": 0.7, "curve": "Natural"},
490
+ "effects": [{"pid": "p4", "type": "rotate3d", "x": 32, "y": 25, "perspective": 300}, {"pid": "p5", "type": "rotation", "angle": -6}, {"pid": "p6", "type": "scale", "scale": 150}, {"pid": "p7", "type": "position", "x": 16, "y": -4}] },
491
+ { "aid": "a3", "name": "Sink out",
492
+ "range": {"basedOn": "lines", "drive": "time", "animate": "out", "around": "line", "order": "backward", "stagger": 0.08, "duration": 0.5, "curve": "Ease In"},
493
+ "effects": [{"pid": "p8", "type": "position", "x": 0, "y": 0, "z": -800}, {"pid": "p9", "type": "opacity", "opacity": 0}] }
494
+ ],
495
+ "textAnimatorCamera": "layer",
496
+ "textAnimatorPerspective": 300
497
+ }
498
+ ```
499
+
449
500
  <!-- KINETIC:END -->