greenlight-motion 0.3.3 → 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,40 +10,95 @@
10
10
 
11
11
  ## Contents
12
12
 
13
- - [Preferred cross transitions](video-transitions.md#preferred-cross-transitions)
14
- - [Transition fields](video-transitions.md#transition-fields)
13
+ - [Preferred cross transitions](#preferred-cross-transitions)
14
+ - [Transition fields](#transition-fields)
15
+ - [Writing a transition on an item](#writing-a-transition-on-an-item)
16
+ - [No transitions on 3D model layers](#no-transitions-on-3d-model-layers)
17
+ - [Which clip moves to make the overlap (`transitionOrder`)](#which-clip-moves-to-make-the-overlap-transitionorder)
18
+ - [Ripple transition](#ripple-transition-is-a-modal-action-not-a-stored-field)
19
+ - [Layer in/out transitions](#layer-inout-transitions)
20
+ - [Directional cross transitions](#directional-cross-transitions)
21
+ - [Exit easing runs forward in time](#exit-easing-runs-forward-in-time)
22
+ - [Advanced layer transitions](#advanced-layer-transitions)
23
+ - [Advanced Movement: perspective, motion path, squeeze opposite](#advanced-movement-perspective-motion-path-squeeze-opposite)
24
+ - [Zoom Ruler](#zoom-ruler)
25
+ - [Gradient Glow Stack](#gradient-glow-stack)
26
+ - [Gradient Map](#gradient-map)
27
+ - [The `Custom` transition](#the-custom-transition--build-the-move-yourself)
15
28
 
16
29
  ## Preferred cross transitions
17
30
 
18
- When the user has not named a transition between two clips, choose from these first (`transition` values): `Cross Fade`, `Advanced Zoom`, `Advanced Movement`, `Circular Wipe`, `Pixel Blocks`, `Camera Whip`, `Zoom Ruler`, `Gradient Glow Stack`, `Gradient Map`. Match the transition to the cut. Use a calm `Cross Fade` between related shots, and save a bold one (`Camera Whip`, `Pixel Blocks`, `Gradient Map`) for a change of scene or a beat. Layer in/out transitions have no preferred list yet.
31
+ When the user has not named a transition between two clips, choose from these first (`transition` values): `Cross Fade`, `Advanced Zoom`, `Advanced Movement`, `Circular Wipe`, `Pixel Blocks`, `Camera Whip`, `Zoom Ruler`, `Gradient Glow Stack`, `Gradient Map`. Match the transition to the cut. Use a calm `Cross Fade` between related shots, and save a bold one (`Camera Whip`, `Pixel Blocks`, `Gradient Map`) for a change of scene or a beat. There is no preferred list for layer in/out transitions.
19
32
 
20
33
  ## Transition fields
21
34
 
22
- **No transitions on 3D model layers.** A `model3d` item is drawn by its own 3D pass, which neither the cross blend nor the in/out animation runs, so during a transition the model simply disappears. The editor does not offer them: write no `transition`, `transitionInType` or `transitionOutType` on a `model3d` item, and no cross `transition` on the item right after one. To bring a model in or out, animate it with keyframes (opacity, scale, position, rotation) instead.
35
+ ### Writing a transition on an item
23
36
 
24
- **Which clip moves to make the overlap.** Optional item field **`transitionOrder`**. A cross transition needs the two clips to overlap, and one of them has to give ground to create that overlap. `"Next over previous"` (the default, and how the editor has always worked) slides **this** clip earlier by `transitionDuration`, onto the tail of the clip before it — that clip does not move, and the timeline gets **shorter** by the overlap. `"Previous over next"` leaves this clip exactly where it is and slides the **previous** clip later instead, so its tail lands on this clip's head; the timeline keeps its **full length** and a gap of the overlap opens in front of the clip that moved (anything already welded to that clip by its own transition travels with it, so earlier overlaps are not pulled apart). The transition itself is **unchanged** either way — same frames, same direction, same duration, same `overlapIn` — so this is purely a layout choice and every render path follows it for free. Read the values from `GET /api/video-editor/capabilities` → `transitions.order`. `POST /api/moodboards/{board_id}/video-projects/from-clips` takes it as `transition_order`.
37
+ A cross transition lives on the item it leads INTO (the item before it in the channel is the outgoing clip); a layer's own entrance and exit live on that layer's item. Both read their values from `GET /api/video-editor/capabilities` → `transitions`. For example, a push up into a second shot, and a title that slides up in and fades out:
25
38
 
26
- **Ripple transition is a modal action, not a stored field.** The transition modal's Cross tab has a "Ripple transition" toggle: on Apply it ALSO moves every free-positioned layer (`freeStart` on an `unsnapped` channel, voice included) that started at/after the incoming clip by however far that clip's drawn start moved — so titles, music and voice after the cut stay in sync with it. Other sequential channels are left alone (they hold no gaps); with `"Previous over next"` the incoming clip does not move, so nothing ripples. Writing `transition` / `transitionDuration` through the project API does NOT ripple: to reproduce it, compute the incoming clip's start before and after from the layout rules above and add that delta to the `freeStart` of every free layer whose drawn start was at/after the old start (skip a clip already carried by its own cross into a moved neighbour — its drawn start follows by itself).
39
+ ```json
40
+ [
41
+ { "id": "shot2", "clipId": "c2", "trimStart": 0, "trimEnd": 4,
42
+ "transition": "Push", "transitionDuration": 0.6, "transitionEasing": "Smooth",
43
+ "transitionOptions": { "direction": "up" } },
44
+ { "id": "title1", "clipId": "t1", "freeStart": 1, "trimStart": 0, "trimEnd": 3,
45
+ "transitionInType": "Slide", "transitionInDuration": 0.8, "transitionInEasing": "Expo Out",
46
+ "transitionInOptions": { "direction": "up" },
47
+ "transitionOutType": "Fade", "transitionOutDuration": 0.8, "transitionOutEasing": "Ease In" }
48
+ ]
49
+ ```
50
+
51
+ Durations are clamped on load, so stay inside them: `transitionDuration` 0.05–4 s (up to 30 s for an installed `local:` transition), `transitionInDuration` / `transitionOutDuration` 0.1–3 s. Each defaults to 1 s, and every easing to `Smooth`. `transition: "None"` and `transitionInType` / `transitionOutType` `"None"` mean no transition.
52
+
53
+ ### No transitions on 3D model layers
54
+
55
+ A `model3d` item is drawn by a 3D pass, which neither the cross blend nor the in/out animation runs, so during a transition the model simply disappears. The editor does not offer them: write no `transition`, `transitionInType` or `transitionOutType` on a `model3d` item, and no cross `transition` on the item right after one. To bring a model in or out, animate it with keyframes (scale, position, rotation) instead; opacity works only outside a 3D Scene (no Scene layer, or a Rig with `renderer: "classic"`), since a model the 3D Scene draws takes no opacity.
56
+
57
+ ### Which clip moves to make the overlap (`transitionOrder`)
58
+
59
+ Optional item field **`transitionOrder`**. A cross transition needs the two clips to overlap, and one of them has to give ground to create that overlap. `"Next over previous"` (the default) slides **this** clip earlier by `transitionDuration`, onto the tail of the clip before it — that clip does not move, and the timeline gets **shorter** by the overlap. `"Previous over next"` leaves this clip exactly where it is and slides the **previous** clip later instead, so its tail lands on this clip's head; the timeline keeps its **full length** and a gap of the overlap opens in front of the clip that moved (anything already welded to that clip by its own transition travels with it, so earlier overlaps are not pulled apart). The transition itself is **unchanged** either way — same frames, same direction, same duration, same `overlapIn` — so this is purely a layout choice and every render path follows it for free. Read the values from `GET /api/video-editor/capabilities` → `transitions.order`. `POST /api/moodboards/{board_id}/video-projects/from-clips` takes it as `transition_order`.
60
+
61
+ ### Ripple transition is a modal action, not a stored field
62
+
63
+ A ripple transition, applied in the editor, ALSO moves every free-positioned layer (`freeStart` on an `unsnapped` channel, voice included) that started at/after the incoming clip by however far that clip's drawn start moved — so titles, music and voice after the cut stay in sync with it. Other sequential channels are left alone (they hold no gaps); with `"Previous over next"` the incoming clip does not move, so nothing ripples. Writing `transition` / `transitionDuration` through the project API does NOT ripple: to reproduce it, compute the incoming clip's start before and after from the layout rules above and add that delta to the `freeStart` of every free layer whose drawn start was at/after the old start (skip a clip already carried by its own cross into a moved neighbour — its drawn start follows by itself).
64
+
65
+ ### Layer in/out transitions
27
66
 
28
67
  Per-item **layer transitions** (in/out animations on one layer): `transitionInType` + `transitionInDuration` (s) + `transitionInEasing`, and `transitionOutType` + `transitionOutDuration` + `transitionOutEasing`. Read allowed values and direction-specific descriptions from `GET /api/video-editor/capabilities` → `transitions.layer.builtIn`; never rely on a copied list. **Direction semantics:** `Slide` and `Wipe` are ONE layer type each; the way goes in the slot's options — `transitionInOptions: { "direction": … }` / `transitionOutOptions` (the catalog lists the values). `Slide`: `left` / `right` = the edge it enters from (IN) and leaves by (OUT); `up` / `down` = the way it moves — `up` rises from the bottom on IN and rises out of the top on OUT. `Wipe`: `leftToRight`, `rightToLeft`, `bottomToTop`, `topToBottom` = the way the edge sweeps (IN reveals, OUT hides). `Zoom In` grows into place, `Zoom Out` shrinks into place; on OUT `Zoom In` pushes toward the camera while fading, `Zoom Out` shrinks away while fading. Pick the OUT direction by the motion you want on screen.
29
68
 
30
- **Directional cross transitions.** `Push` (both clips travel together — the next pushes the current one out), `Parallax` (the next slides in while the current drifts back slower) and `Wipe` (a hard edge sweeps across) are one type each; the way goes in `transitionOptions.direction`. `Push` / `Parallax`: `left`, `right`, `up`, `down` = the way the clips MOVE (`up`: the next clip rises in from below). `Wipe`: `leftToRight` (default), `rightToLeft`, `bottomToTop`, `topToBottom`. The old per-direction names (`Slide Left/Right`, `Push Up/Down`, `Parallax Left/Right/Up/Down`, `Wipe Reverse`; layer `Slide Left…Down`, `Wipe Left…Down`) are gone from the catalog: a saved project that holds one is rewritten to the merged type + direction when the editor loads it — write the new form.
69
+ ### Directional cross transitions
70
+
71
+ `Push` (both clips travel together — the next pushes the current one out), `Parallax` (the next slides in while the current drifts back slower) and `Wipe` (a hard edge sweeps across) are one type each; the way goes in `transitionOptions.direction`. `Push` / `Parallax`: `left`, `right`, `up`, `down` = the way the clips MOVE (`up`: the next clip rises in from below). `Wipe`: `leftToRight` (default), `rightToLeft`, `bottomToTop`, `topToBottom`. Per-direction names (`Slide Left/Right`, `Push Up/Down`, `Parallax Left/Right/Up/Down`, `Wipe Reverse`; layer `Slide Left…Down`, `Wipe Left…Down`) are not in the catalog; the editor rewrites one it finds into the type + direction on load. Always write the type + direction.
72
+
73
+ ### Exit easing runs forward in time
74
+
75
+ `transitionOutEasing` describes what you SEE across the exit, not the layer's presence counting down: **`Ease In` accelerates into the cut** (the layer is still speeding up on the last frame — use it, or `Expo In`/`Cubic In`, for an exit that flies out rather than settling), `Ease Out` leaves fast and eases to a stop, and the symmetric curves (`Smooth`, the default, `Natural` and the other `* Smooth` curves: `Sine Smooth`, `Expo Smooth`, `Back Smooth`, `AE Smooth`) do both. This matters most with `Advanced Zoom` / `Advanced Movement`, whose `blurProfile: "velocity"` ("Follow speed") sets the blur from the curve's slope: with an accelerating curve the blur is at its maximum on the final frame, with `Smooth` it peaks mid-exit and is gone by the end. Set `blurProfile: "constant"` if you want it flat regardless of the curve.
76
+
77
+ ### Advanced layer transitions
78
+
79
+ `Advanced Zoom`, `Advanced Movement`, `3D Carousel`, `Camera Whip`, `Gradient Glow Stack`, `Gradient Map`, `Pixel Blocks` and `Zoom Ruler` are also layer in/out types, carrying the same option schema as the cross transitions of the same name — they are those transitions driven one-sided (an entrance is the incoming lane, an exit the outgoing one). Their options go in **`transitionInOptions` / `transitionOutOptions`**, NOT `transitionOptions` (that field is the cross slot's alone). Read `optionDefaults` / `optionFields` per slot from `GET /api/video-editor/capabilities` → `transitions.layer.builtIn` (entries with `kind: "advanced"`); the catalog already omits the cross-only controls, so anything it lists is settable on a layer slot. Two behaviours to plan around: `blendWindow` is the layer's opacity ramp rather than a cut (`0` = no ramp, the layer is simply on from `blendPoint`; on a cross slot `0` is a hard cut between the two clips), and Advanced Zoom/Movement operate inside the **layer's own box** — on a layer smaller than the frame, set `edgeMode: "none"` unless you want the mirrored tile fill. A cross transition on the same clip takes precedence over an advanced in/out for the length of its overlap. `Camera Whip` one-sided: the entrance is the incoming lane (the camera whips off nothing and lands on the layer; the layer is present from `cutAt` ± `blendWindow` on), the exit the outgoing lane (the layer is whipped off and gone after `cutAt`), so the layer's arrival/departure sits at `cutAt` % of the slot's duration — set `cutAt` low on an entrance / high on an exit to keep more of the layer on screen. The empty lane draws nothing (lower channels show through) and the flash lights only the layer's own footprint. `Pixel Blocks` one-sided: the blocks bring the layer in over what is under it (entrance) or take it away (exit); `mode` `cross`, `appear` and `disappear` all play that same way on a layer slot, and `colour` lands the colour on the layer's own pixels first. `pixelate` pixelates the layer itself (as it arrives, or as it goes). `Zoom Ruler` one-sided: the entrance opens the layer inside the growing ruler box over what is under it, the exit keeps the layer outside the box until the box clears the frame; the guides and corner marks draw over the lower channels on both. The zoom scales the incoming clip only, so the Out slot has no `zoom` / `zoomDirection` / `zoomEasing`.
80
+
81
+ ### Advanced Movement: perspective, motion path, squeeze opposite
82
+
83
+ Three option groups beyond the field list's obvious numbers. **Perspective** tilts each clip like a 3D card as it travels: `perspective` (°, ±80, 0 = off), `perspectiveMode` (`"lean"` = both clips tip into the move and settle back; `"swing"` = outgoing turns 0 → θ while incoming arrives −θ → 0, one continuous card turn), `perspectiveDepth` (camera distance in frame heights — lower = harsher foreshortening), `perspectiveAxis` (`"auto"` follows the travel), `perspectiveAttach` (`"plate"`/`"camera"`), `perspectiveShade` (darkens the receding half). **Motion path**: set `travelMode: "path"` and give `travelPath` an object — `{ "points": [{ "x": 14, "y": 26 }, { "x": 68, "y": 26 }, { "x": 68, "y": 78 }], "shares": [100, 100] }` — points in percent-of-comp (max 12; the move is RELATIVE to the first point, and a path needs ≥ 2 points or the option falls back to straight), `shares` one weight per segment (5–500, normalized) setting how long the move spends reaching each next point; `pathSmooth` (0–100) rounds the curve through the anchors. Both clips ride the same curve — the incoming lands exactly in place — and `direction: "from"` runs it in reverse, shares included. `usePointDistance` is ignored in path mode; `travelStrength` still scales the drawn move. **`squeezeOpposite: true`** mirrors the squeeze on the incoming clip (1 + s vs 1 − s) for a counter-move hand-off.
84
+
85
+ ### Zoom Ruler
31
86
 
32
- **Exit easing runs forward in time.** `transitionOutEasing` describes what you SEE across the exit, not the layer's presence counting down: **`Ease In` accelerates into the cut** (the layer is still speeding up on the last frame — use it, or `Expo In`/`Cubic In`, for an exit that flies out rather than settling), `Ease Out` leaves fast and eases to a stop, and the symmetric curves (`Smooth`, the default, and every `* In Out`) do both. This matters most with `Advanced Zoom` / `Advanced Movement`, whose `blurProfile: "velocity"` ("Follow speed") sets the blur from the curve's slope: with an accelerating curve the blur is at its maximum on the final frame, with `Smooth` it peaks mid-exit and is gone by the end. Set `blurProfile: "constant"` if you want it flat regardless of the curve.
87
+ A cross type and a layer in/out type whose Shape Rulers box is the clip mask: the outgoing clip shows outside the box and the incoming one inside, until the box clears the frame. It eases itself — `transitionOptions.curve` (`"reference"`, a measured open → hold → burst growth, or one of the easings that option's `optionFields` entry lists) replaces `transitionEasing`, and `holdSize` (15–75 %) sets where the reference curve's hold sits. Its catalog `defaultDuration` is 1.6 s; write `transitionDuration` explicitly. `point` (% of the frame, y down) aims the box; `zoom` (0–40 %) with `zoomDirection` `"in"` (the incoming clip grows into place) or `"settle"` (it shrinks into place) scales the incoming clip about the box centre; `spacingX` / `spacingY` stand the guides off the mask edge. The line and corner options mirror the `shapeRulers` overlay effect's params.
33
88
 
34
- **Advanced layer transitions.** `Advanced Zoom`, `Advanced Movement`, `3D Carousel`, `Camera Whip`, `Gradient Glow Stack`, `Gradient Map` and `Pixel Blocks` are also layer in/out types, carrying the same option schema as the cross transitions of the same name — they are those transitions driven one-sided (an entrance is the incoming lane, an exit the outgoing one). Their options go in **`transitionInOptions` / `transitionOutOptions`**, NOT `transitionOptions` (that field is the cross slot's alone). Read `optionDefaults` / `optionFields` per slot from `GET /api/video-editor/capabilities` → `transitions.layer.builtIn` (entries with `kind: "advanced"`); the catalog already omits the cross-only controls, so anything it lists is settable on a layer slot. Two behaviours to plan around: `blendWindow` is the layer's opacity ramp rather than a cut (`0` = no ramp, the layer is simply on from `blendPoint`; on a cross slot `0` is a hard cut between the two clips), and Advanced Zoom/Movement operate inside the **layer's own box** — on a layer smaller than the frame, set `edgeMode: "none"` unless you want the mirrored tile fill. A cross transition on the same clip takes precedence over an advanced in/out for the length of its overlap. `Camera Whip` one-sided: the entrance is the incoming lane (the camera whips off nothing and lands on the layer; the layer is present from `cutAt` ± `blendWindow` on), the exit the outgoing lane (the layer is whipped off and gone after `cutAt`), so the layer's arrival/departure sits at `cutAt` % of the slot's duration — set `cutAt` low on an entrance / high on an exit to keep more of the layer on screen. The empty lane draws nothing (lower channels show through) and the flash lights only the layer's own footprint. `Pixel Blocks` one-sided: the blocks bring the layer in over what is under it (entrance) or take it away (exit); `mode` `cross`, `appear` and `disappear` all play that same way on a layer slot, and `colour` lands the colour on the layer's own pixels first. `pixelate` pixelates the layer itself (as it arrives, or as it goes).
89
+ ### Gradient Glow Stack
35
90
 
36
- **Advanced Movement: perspective, motion path, squeeze opposite.** Three option groups beyond the field list's obvious numbers. **Perspective** tilts each clip like a 3D card as it travels: `perspective` (°, ±80, 0 = off), `perspectiveMode` (`"lean"` = both clips tip into the move and settle back; `"swing"` = outgoing turns 0 → θ while incoming arrives −θ → 0, one continuous card turn), `perspectiveDepth` (camera distance in frame heights — lower = harsher foreshortening), `perspectiveAxis` (`"auto"` follows the travel), `perspectiveAttach` (`"plate"`/`"camera"`), `perspectiveShade` (darkens the receding half). **Motion path**: set `travelMode: "path"` and give `travelPath` an object — `{ "points": [{ "x": 14, "y": 26 }, { "x": 68, "y": 26 }, { "x": 68, "y": 78 }], "shares": [100, 100] }` — points in percent-of-comp (max 12; the move is RELATIVE to the first point, and a path needs ≥ 2 points or the option falls back to straight), `shares` one weight per segment (5–500, normalized) setting how long the move spends reaching each next point; `pathSmooth` (0–100) rounds the curve through the anchors. Both clips ride the same curve — the incoming lands exactly in place — and `direction: "from"` runs it in reverse, shares included. `usePointDistance` is ignored in path mode; `travelStrength` still scales the drawn move. **`squeezeOpposite: true`** mirrors the squeeze on the incoming clip (1 + s vs 1 − s) for a counter-move hand-off.
91
+ A cross type and a layer in/out type: a stack of huge, soft gradient glows (2–6 layers) sweeps through the frame in a stagger and the incoming clip appears behind it — the outgoing clip never shows again once the stack has passed. It eases itself: each layer runs its own staggered clock off `easing` (one of the 20 easings that option's `optionFields` entry lists, default `"Natural"`), which replaces `transitionEasing`. Its catalog `defaultDuration` is 2.27 s; write `transitionDuration` explicitly. `direction` (`up` = bottom → top, `down`, `left`, `right`, `upRight`, `upLeft`, `downRight`, `downLeft`, or `angle` with `angle` in degrees, 90 = up) sets the travel; `stagger` (0–30 %) the launch gap between layers. `layers` sets how many of `color1` … `color6` are used — `color1` is the bottom layer and leads, the last used colour is the top layer. `shape` `"circle"` sweeps curved bands, `"square"` straight ones (`roundness` rounds its corners); `size` and `softness` (%) scale the layers and their blur relative to the frame. `fadeOut: true` fades the shapes over the last `fadeLength` s (capped at 85 % of the duration) and paces the sweep so the stack covers the frame when the fade starts — the clips do not dissolve; `crossfade: true` instead dissolves the clips for exactly as long as any shape is in view. `blend` (`normal`, `screen`, `add`) and `grain` / `grainSize` (in 1080p pixels) finish the look. `covers` sets where the glow is drawn: `"box"` (default) sizes the sweep to the layer's box and clips it there (a text layer's box is its laid-out text block, as the selection box shows it; on a cross, the union of both clips' boxes), `"shape"` also keeps it to the layer's own pixels (its alpha: inside the letters of a text, inside a circle), `"frame"` sweeps the whole composition. On a full-frame clip all three look the same. On a layer slot (options in `transitionInOptions` / `transitionOutOptions`) the empty side is transparent: the glow sweeps over the lower channels and the layer appears (entrance) or goes (exit) behind it.
37
92
 
38
- **Zoom Ruler.** A cross type whose Shape Rulers box is the clip mask: the outgoing clip shows outside the box and the incoming one inside, until the box clears the frame. It eases itself — `transitionOptions.curve` (`"reference"`, a measured open → hold → burst growth, or any easing name) replaces `transitionEasing`, and `holdSize` (15–75 %) sets where the reference curve's hold sits. Its catalog `defaultDuration` is 1.6 s; write `transitionDuration` explicitly. `point` (% of the frame, y down) aims the box; `zoom` (0–40 %) with `zoomDirection` `"in"` (the incoming clip grows into place) or `"settle"` (it shrinks into place) scales the incoming clip about the box centre; `spacingX` / `spacingY` stand the guides off the mask edge. The line and corner options mirror the `shapeRulers` overlay effect's params.
93
+ ### Gradient Map
39
94
 
40
- **Gradient Glow Stack.** A cross type and a layer in/out type: a stack of huge, soft gradient glows (2–6 layers) sweeps through the frame in a stagger and the incoming clip appears behind it — the outgoing clip never shows again once the stack has passed. It eases itself: each layer runs its own staggered clock off `easing` (any easing name, default `"Natural"`), which replaces `transitionEasing`. Its catalog `defaultDuration` is 2.27 s; write `transitionDuration` explicitly. `direction` (`up` = bottom → top, `down`, `left`, `right`, `upRight`, `upLeft`, `downRight`, `downLeft`, or `angle` with `angle` in degrees, 90 = up) sets the travel; `stagger` (0–30 %) the launch gap between layers. `layers` sets how many of `color1` … `color6` are used — `color1` is the bottom layer and leads, the last used colour is the top layer. `shape` `"circle"` sweeps curved bands, `"square"` straight ones (`roundness` rounds its corners); `size` and `softness` (%) scale the layers and their blur relative to the frame. `fadeOut: true` fades the shapes over the last `fadeLength` s (capped at 85 % of the duration) and paces the sweep so the stack covers the frame when the fade starts — the clips do not dissolve; `crossfade: true` instead dissolves the clips for exactly as long as any shape is in view. `blend` (`normal`, `screen`, `add`) and `grain` / `grainSize` (in 1080p pixels) finish the look. `covers` sets where the glow is drawn: `"box"` (default) sizes the sweep to the layer's box and clips it there (a text layer's box is its laid-out text block, as the selection box shows it; on a cross, the union of both clips' boxes), `"shape"` also keeps it to the layer's own pixels (its alpha: inside the letters of a text, inside a circle), `"frame"` sweeps the whole composition. On a full-frame clip all three look the same. On a layer slot (options in `transitionInOptions` / `transitionOutOptions`) the empty side is transparent: the glow sweeps over the lower channels and the layer appears (entrance) or goes (exit) behind it.
95
+ A cross type and a layer in/out type, decoded from an After Effects gradient-map pack: the clip turns into a luminance gradient map of itself (a looping palette that spins), then the real picture returns luminance-ordered — through the shadows first by default — while it settles from a zoom. A cross runs three stages whose lengths are the shares `mapIn`, `swap`, `reveal` (%, normalized): the map builds over the outgoing clip, the incoming clip's map wipes in over it brightest-first, then that map dissolves into the incoming clip. An entrance skips `mapIn` (the map arrives over the lower channels), an exit skips `reveal` (the outgoing map dissolves to nothing, darkest first) — the catalog omits the share a slot ignores. It eases itself: `easing` (one of the 20 easings that option's `optionFields` entry lists; default `"Linear"`, as the AE reference) replaces `transitionEasing`; the zoom keeps the reference's own curve. Its catalog `defaultDuration` is 2.5 s; write `transitionDuration` explicitly. `palette`: `rose` (default), `amber`, `fireIce`, `pastel`, `cream`, `peach`, `lime`, `ultraviolet`, `rainbow`, or `custom` with `color1`…`color4` (shadows → highlights, looping). `hue` (°), `lightness` and `saturation` (−100…100) recolour the palette like AE's Color Balance (HLS); `spin` (°/s) turns it; `bandsStart` / `bandsEnd` repeat it (more bands = posterized stripes). `order` `"shadows"` or `"highlights"` picks which pixels get their real colour back first; `softness` (%) is the wipe's edge; `zoom` (100–200 %) the push the clips settle from. `accent`: `none`, `dots` (a dotted ring), `plus` (a plus-sign grid inside a growing disc) or `pixels` (a flickering pixel ring), coloured by `accentColor`, scaled by `accentStrength` (%). `pointReveal: true` ties the effect to `point` (`{x, y}` % of the frame, y down): the gradient appears there first and spreads out, then the real picture comes back from the point and spreads out the same way (one direction, like Circular Wipe); `pointStrength` (0–100 %, default 75) sets how much the point leads over the brightness order (100 % = plain soft circles).
41
96
 
42
- **Gradient Map.** A cross type and a layer in/out type, decoded from an After Effects gradient-map pack: the clip turns into a luminance gradient map of itself (a looping palette that spins), then the real picture returns luminance-ordered — through the shadows first by default — while it settles from a zoom. A cross runs three stages whose lengths are the shares `mapIn`, `swap`, `reveal` (%, normalized): the map builds over the outgoing clip, the incoming clip's map wipes in over it brightest-first, then that map dissolves into the incoming clip. An entrance skips `mapIn` (the map arrives over the lower channels), an exit skips `reveal` (the outgoing map dissolves to nothing, darkest first) — the catalog omits the share a slot ignores. It eases itself: `easing` (any easing name, default `"Linear"`, as the AE reference) replaces `transitionEasing`; the zoom keeps the reference's own curve. Its catalog `defaultDuration` is 2.5 s; write `transitionDuration` explicitly. `palette`: `rose` (default), `amber`, `fireIce`, `pastel`, `cream`, `peach`, `lime`, `ultraviolet`, `rainbow`, or `custom` with `color1`…`color4` (shadows → highlights, looping). `hue` (°), `lightness` and `saturation` (−100…100) recolour the palette like AE's Color Balance (HLS); `spin` (°/s) turns it; `bandsStart` / `bandsEnd` repeat it (more bands = posterized stripes). `order` `"shadows"` or `"highlights"` picks which pixels get their real colour back first; `softness` (%) is the wipe's edge; `zoom` (100–200 %) the push the clips settle from. `accent`: `none`, `dots` (a dotted ring), `plus` (a plus-sign grid inside a growing disc) or `pixels` (a flickering pixel ring), coloured by `accentColor`, scaled by `accentStrength` (%). `pointReveal: true` ties the effect to `point` (`{x, y}` % of the frame, y down): the gradient appears there first and spreads out, then the real picture comes back from the point and spreads out the same way (one direction, like Circular Wipe); `pointStrength` (0–100 %, default 75) sets how much the point leads over the brightness order (100 % = plain soft circles).
97
+ ### The `Custom` transition — build the move yourself
43
98
 
44
- **The `Custom` transition — build the move yourself.** `"Custom"` appears in the cross list and in both layer slots, and unlike every other type it carries no built-in move: what it does comes entirely from a config object on the item. For API-authored items, **an absent config or empty lane produces a plain dissolve**: the loader defaults `moveX`/`moveY` to 0 and `opacityFrom` to 0. The **Transition Builder UI** uses a different starter/reset preset (`next.moveX: 100`, `prev.moveX: 0`), which slides the incoming clip from the right. Write the lane fields explicitly to reproduce either result; do not infer API defaults from the builder preset.
99
+ `"Custom"` appears in the cross list and in both layer slots, and unlike every other type it carries no built-in move: what it does comes entirely from a config object on the item. For API-authored items, **an absent config or empty lane produces a plain dissolve**: the loader defaults `moveX`/`moveY` to 0 and `opacityFrom` to 0. The editor's Transition Builder starts from a different preset (`next.moveX: 100`, `prev.moveX: 0`), which slides the incoming clip from the right. Write the lane fields explicitly to reproduce either result; do not infer API defaults from that preset.
45
100
 
46
- - **Where it goes.** A **cross** slot reads `customTransitionConfig`, which holds **two lanes**: `{ "prev": {…}, "next": {…} }` — `prev` is the OUTGOING clip, `next` the INCOMING one. A layer **entrance** reads `customTransitionInConfig` and an **exit** `customTransitionOutConfig`, each a single bare lane with no `prev`/`next` wrapper. A bare lane written to `customTransitionConfig` is read as its `next` (a legacy shape that still loads); the `prev` lane then holds its position and fades out.
101
+ - **Where it goes.** A **cross** slot reads `customTransitionConfig`, which holds **two lanes**: `{ "prev": {…}, "next": {…} }` — `prev` is the OUTGOING clip, `next` the INCOMING one. A layer **entrance** reads `customTransitionInConfig` and an **exit** `customTransitionOutConfig`, each a single bare lane with no `prev`/`next` wrapper. A bare lane written to `customTransitionConfig` is read as its `next`; the `prev` lane then holds its position and fades out.
47
102
  - **A lane describes the pose AWAY FROM REST, and the two lanes travel it in opposite directions.** The incoming clip starts at that pose and arrives at rest; the outgoing clip starts at rest and leaves toward it. So one object means "flies in from the right" on `next` and "flies out to the right" on `prev` — you do not negate anything to reverse a lane.
48
103
 
49
104
  ```json
@@ -54,6 +109,6 @@ Per-item **layer transitions** (in/out animations on one layer): `transitionInTy
54
109
  ```
55
110
 
56
111
  - **The move.** `moveX` / `moveY` are the offset as a **% of the frame** (−200…200), `moveX` positive = right, **`moveY` positive = down**. `scaleFrom` (0.1–3) and `opacityFrom` (0–1) are the scale and alpha at that pose — `opacityFrom: 0` (the default) is what makes an unconfigured `Custom` a dissolve. `rotation` (−360…360) is roll and `rotateX` / `rotateY` (−180…180) the 3D tilt. `originX` / `originY` (0–100 % of the layer box, default 50) is the pivot the rotation and scale turn about. `tile: true` fills the vacated area with mirrored copies of the clip instead of transparency.
57
- - **The effects.** `fxZoom` (−300…300 %), `fxBlur` (0–60 px) with `fxBlurType` (`gaussian` | `directional` | `radial`), `fxRgbSplit` (0–300) with `fxRgbAlong` (`motion` | `horizontal` | `radial`), `fxOptics` and `fxFisheye` (−200…200) with `fxFisheyeModel` (`spherical` | `barrel`) / `fxFisheyeCentre` (`point` | `frame`) / `fxFisheyeFit`, `fxFlash` (0–200) with `fxFlashColor` (`#rrggbb`), `fxOpacityDip` (0–100), and `fxWiggle` (0–100) with `fxWiggleFreq` (1–40), `fxWiggleRotate` (0–8) and `fxWiggleSeed` (1–99). **Amount `0` is off for every group**, so a config that omits them behaves exactly as one written before they existed. `fxPointX` / `fxPointY` (0–100 %, default 50) is the pivot the zoom and the radial modes aim through — separate from `originX`/`originY`.
112
+ - **The effects.** `fxZoom` (−300…300 %), `fxBlur` (0–60 px) with `fxBlurType` (`gaussian` | `directional` | `radial`), `fxRgbSplit` (0–300) with `fxRgbAlong` (`motion` | `horizontal` | `radial`), `fxOptics` and `fxFisheye` (−200…200) with `fxFisheyeModel` (`spherical` | `barrel`) / `fxFisheyeCentre` (`point` | `frame`) / `fxFisheyeFit`, `fxFlash` (0–200) with `fxFlashColor` (`#rrggbb`), `fxOpacityDip` (0–100), and `fxWiggle` (0–100) with `fxWiggleFreq` (1–40), `fxWiggleRotate` (0–8) and `fxWiggleSeed` (1–99). **Amount `0` is off for every group**, so leave out any group you don't use. `fxPointX` / `fxPointY` (0–100 %, default 50) is the pivot the zoom and the radial modes aim through — separate from `originX`/`originY`.
58
113
  - **`fxProfile` shapes every fx across the transition**: `"ramp"` (default — rises with it), `"peak"` (rises and falls, strongest mid-way, which is what a blur or a flash on a cut wants) or `"constant"` (flat).
59
- - Values outside these ranges are clamped on load and unknown keys are stripped, so round-trip only what you mean to change. **A new key must be added to `sanitizeCustomLayerConfig` in `VideoEditor.jsx` or autosave drops it.** Builder defaults and field ranges live in `frontend/src/components/videoEditor/customTransitionConfig.js`; persisted-item defaults live in `sanitizeCustomLayerConfig` in `VideoEditor.jsx`; the runtime is `applyCustomLaneEffects` in `gsapTransitions.js`.
114
+ - Values outside these ranges are clamped on load and unknown keys are dropped on save, so write only the keys above and round-trip only what you mean to change.