@framefields/node-compositor 0.0.0-stage → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +22 -0
  2. package/.turbo/turbo-test.log +21 -0
  3. package/CHANGELOG.md +32 -0
  4. package/LICENCE +202 -0
  5. package/SKILL.md +370 -0
  6. package/dist/index-B0ir9Z7o.d.mts +139 -0
  7. package/dist/index-B0ir9Z7o.d.mts.map +1 -0
  8. package/dist/index.d.mts +230 -0
  9. package/dist/index.d.mts.map +1 -0
  10. package/dist/index.mjs +674 -0
  11. package/dist/index.mjs.map +1 -0
  12. package/dist/renderer.d.mts +2 -0
  13. package/dist/renderer.mjs +3 -0
  14. package/dist/renderers-DoYYCzgA.mjs +3326 -0
  15. package/dist/renderers-DoYYCzgA.mjs.map +1 -0
  16. package/package.json +51 -3
  17. package/src/index.ts +2 -0
  18. package/src/renderers/audio-processor.ts +123 -0
  19. package/src/renderers/container-bg.test.ts +22 -0
  20. package/src/renderers/container-bg.ts +15 -0
  21. package/src/renderers/index.ts +9 -0
  22. package/src/renderers/transform.test.ts +120 -0
  23. package/src/renderers/transform.ts +101 -0
  24. package/src/renderers/transform3d.test.ts +290 -0
  25. package/src/renderers/transform3d.ts +428 -0
  26. package/src/renderers/webgpu-renderer.ts +4038 -0
  27. package/src/shared/audio-reactive.test.ts +224 -0
  28. package/src/shared/bezier-solver.test.ts +92 -0
  29. package/src/shared/bezier-solver.ts +257 -0
  30. package/src/shared/compiler.test.ts +1309 -0
  31. package/src/shared/compiler.ts +996 -0
  32. package/src/shared/config.ts +44 -0
  33. package/src/shared/index.ts +3 -0
  34. package/src/shared/kinetic-typography.test.ts +138 -0
  35. package/src/shared/presets.test.ts +217 -0
  36. package/src/shared/presets.ts +644 -0
  37. package/src/shared/processor.ts +105 -0
  38. package/tsconfig.json +13 -0
  39. package/tsdown.config.ts +13 -0
  40. package/README.md +0 -3
package/SKILL.md ADDED
@@ -0,0 +1,370 @@
1
+ ---
2
+ name: Compositor
3
+ nodeType: Compositor
4
+ summary: >
5
+ Composes media inputs (Text, Image, SVG, Audio, Caption, Video, GIF, and Lottie) into a single
6
+ image or video file with an HTML-like auto-layout engine. The composition document is a single
7
+ recursive `layout` code tree (flex/block/box/text/media) with per-node keyframe animation —
8
+ deterministic: identical pixels for the same document and frame, in preview AND final render.
9
+ triggers:
10
+ - compositor
11
+ - composite
12
+ - layer
13
+ - merge media
14
+ - layout
15
+ - flex
16
+ - overlay
17
+ - picture in picture
18
+ - video layout
19
+ - title card
20
+ ---
21
+
22
+ # Compositor
23
+
24
+ ## What It Does
25
+ Composes media inputs into a single image or video using a **layout code tree** — the ONE
26
+ source of truth for the composition. Agents author `config.layout`: a recursive tree of
27
+ layout nodes (`flex` / `block` / `box` / `text` / `media`) with HTML-like auto-layout
28
+ (`dir`, `gap`, `padding`, `justify`, `align`, `wrap`) and per-node keyframe animations.
29
+ Rendering is deterministic: `(document, frame) → pixels`, identical in preview and final render.
30
+
31
+ ## When to Use
32
+ - **Title cards / hero layouts:** Flex stacks with title + subtitle text and box chips (see example below).
33
+ - **Overlays / Watermarks:** Absolute-positioned text or media over video/image backgrounds.
34
+ - **Picture-in-Picture:** Multiple videos/images arranged by a flex/block tree or absolute placement.
35
+ - **Timeline composition:** Per-node `startFrame` / `durationFrames` + keyframe tracks.
36
+ - **Visual styling:** Box fills + radius, text styling, shadows, keyframe motion.
37
+
38
+ ## Inputs
39
+ This node uses **Variable Inputs**. You can add dynamically named input handles of the following types:
40
+ - `Text`
41
+ - `Image`
42
+ - `Video`
43
+ - `Audio`
44
+ - `Caption`
45
+ - `SVG`
46
+ - `GIF`
47
+ - `Lottie`
48
+ - `Signal`
49
+
50
+ ## Config
51
+ | Field | Type | Range | Default | Description |
52
+ |-------|------|-------|---------|-------------|
53
+ | width | number | 1–4096 | 1080 | Canvas width in pixels. |
54
+ | height | number | 1–4096 | 1080 | Canvas height in pixels. |
55
+ | backgroundColor | string | Hex/RGB CSS Color | undefined | Background color of the compositor canvas. |
56
+ | volume | number | 0–1 | 1 | Overall master audio volume scaling. |
57
+ | fps | number | 1–120 | 24 | Frames per second for video output. |
58
+ | mode | string | `"Video"` or `"Image"` | `"Video"` | Explicitly configures compositor rendering/output mode. |
59
+ | layout | array | Array of Layout Nodes | `[]` | The composition document: a recursive tree of layout nodes. |
60
+
61
+ > **`type` vs `kind`:** `type` is an INPUT DataType (`Text`, `Image`, `Video`, `Audio`,
62
+ > `Caption`, `SVG`, `GIF`, `Lottie` …) and never appears on layout nodes. The layout type of
63
+ > a node is its **`kind`**: `flex` | `block` | `box` | `text` | `media` | `shape` | `chart`.
64
+
65
+ ---
66
+
67
+ ### Layout Node Schema
68
+ Common fields (every node):
69
+ - **`id`** (string, required): Unique node id — also keys the timeline.
70
+ - **`kind`** (string, required): `"flex"` | `"block"` | `"box"` | `"text"` | `"media"` | `"shape"`.
71
+ - **`inputHandleId`** (string, optional): Graph binding — which connected input this node renders (for text/media nodes).
72
+ > [!WARNING]
73
+ > Do not place a handle label in `inputHandleId` when updating an existing live Compositor. The renderer may treat the media as unbound and produce a blank layer. Use the actual dynamic input handle ID returned from the live Compositor node.
74
+ - **`position`** (string, optional): `"relative"` (default, in-flow) or `"absolute"` (out-of-flow; placed by `x`/`y`).
75
+ - **`x` / `y`** (number, optional): Offset from the parent's content box (absolute placement / transform base).
76
+ - **`width` / `height`** (SizeSpec, optional): `number` (pixels), `"auto"` (content), `"fit"` (fit content), or `"fill"` (fill the parent). `block` defaults to `"fill"` width.
77
+ - **`grow`** (number, optional): Flex-grow weight — extra main-axis space is split proportionally.
78
+ - **`flexShrink`** (number, optional): Flex shrink factor (Yoga).
79
+ - **`flexBasis`** (number, optional): Flex basis in pixels.
80
+ - **`alignSelf`** (string, optional): Per-child cross-axis override (`"auto"` | `"start"` | `"center"` | `"end"` | `"stretch"` | `"baseline"`).
81
+ - **`aspectRatio`** (number, optional): Intrinsic aspect ratio (`width / height`).
82
+ - **`zIndex`** (number, optional, default 0): Stack order **within the same parent level**. Higher renders on top.
83
+ - **`hidden`** (boolean, optional): Skips drawing the node.
84
+ - **`opacity`** (number, optional, 0–1, default 1).
85
+ - **`blendMode`** (string, optional, default `"normal"`): Blend mode used when compositing the node (`"normal"`, `"multiply"`, `"screen"`, `"overlay"`, `"darken"`, `"lighten"`, `"color-dodge"`, `"color-burn"`, `"hard-light"`, `"soft-light"`, `"difference"`, `"exclusion"`, `"hue"`, `"saturation"`, `"color"`, `"luminosity"`, `"source-over"`, `"source-in"`, `"source-out"`, `"source-atop"`, `"destination-over"`, `"destination-in"`, `"destination-out"`, `"destination-atop"`, `"lighter"`, `"copy"`, `"mask-in"`, `"mask-out"`, `"xor"`).
86
+ - **`rotation`** (degrees) / **`scale`** (multiplier) / **`anchorX`**, **`anchorY`** (0–1): Node transform.
87
+ - **`rotateX`** / **`rotateY`** / **`rotateZ`** (degrees, default 0): 3D out-of-plane rotation (pitch, yaw, roll).
88
+ - **`perspective`** (number, px distance, default 0 = disabled): Virtual camera distance for 3D perspective foreshortening.
89
+ - **`translateZ`** (number, px depth, default 0): Translation along depth Z axis (moves closer/further under perspective).
90
+ - **`perspectiveOriginX`**, **`perspectiveOriginY`** (0–1, default 0.5): 3D perspective vanishing point anchor.
91
+ - **`backfaceVisibility`** (`"visible"` | `"hidden"`, default `"visible"`): When `"hidden"`, culls the layer when rotated > 90° away from the camera.
92
+ - **`transformStyle`** (`"flat"` | `"preserve-3d"`, default `"flat"`): 3D transform rendering style.
93
+ - **`startFrame`** / **`durationFrames`** (integer, optional): Node visibility window on the master timeline (frames).
94
+ - **`deflicker`** (object, optional): Temporal de-flickering and optical flow motion warping options (`blendWeight`, `disocclusionThreshold`, `searchRadius`).
95
+ - **`relighting`** / **`relight`** (object, optional): Screen-space 3D normal relighting options (`lightType`, `intensity`, `lightPosX`, `lightPosY`, `lightPosZ`, `lightRadius`, `specularStrength`, `roughness`, `metallic`, `ambientIntensity`, `depthScale`, `normalTexture`).
96
+ - **`effects`** (array, optional): Post-processing effect pipeline attached to the node (e.g. `Effect.deflicker()`, `Effect.relight3d()`).
97
+ - **`animation`** (object, optional): Track-based keyframes (§ Animation Schema).
98
+
99
+ Container styles (flex/block/box with children):
100
+ - **`dir`** (flex only): `"row"` (default) or `"column"`.
101
+ - **`gap`** (number): Space between children along the main axis.
102
+ - **`padding`** (number): Inset of the content box.
103
+ - **`justify`** (flex only): `start` (default) | `center` | `end` | `space-between` | `space-around`.
104
+ - **`align`** (flex/block): `start` (default) | `center` | `end` | `stretch`.
105
+ - **`wrap`** (flex only, boolean): Allow main-axis wrapping.
106
+ - **`overflow`** (`"hidden"` (default) | `"visible"`): Clip children to container bounds (`hidden`) or allow drawing outside (`visible`). A hidden container with a `borderRadius` clips to its rounded corners, animated radius and size included (an iris mask is a box closing its `width`, `height` and `borderRadius`).
107
+ - **`staggerFrames`** (number, optional, default 0): Automatic frame offset sequentially applied to child nodes.
108
+ - **`staggerDirection`** (`"forward"` | `"reverse"` | `"center-out"`, default `"forward"`): Traversal order when computing child stagger delays.
109
+
110
+ Per-kind fields:
111
+ - **`box`**: `background` (CSS color, also accepts gradients), `borderRadius` (number), `padding`. A `box` with children behaves like a column container.
112
+ - **`text`**: `text` (string), `fontSize`, `fontFamily`, `fontWeight`, `fontStyle`, `fill` (text color), `align`, `verticalAlign` (`top` default | `middle` | `bottom`: places the text block in a `height` taller than it; ignored for path text), `lineHeight`, `letterSpacing`, `textShadow`, `shadows`, `background` (rounded text box fill), `borderRadius`, `padding`.
113
+ - **Kinetic Typography Animators (`animators`)**: Array of text animators applied sequentially at the Slug GPU glyph instancing stage:
114
+ - `id` (string): Unique animator identifier.
115
+ - `unit` (`"character"` | `"word"` | `"line"`, default `"character"`): Granularity of text breakdown.
116
+ - `rangeStart` (number, 0–1, default 0): Normalized selector start bound.
117
+ - `rangeEnd` (number, 0–1, default 1): Normalized selector end bound.
118
+ - `offset` (number, -1 to 1, default 0): Normalized selection window phase offset (keyframeable via `offset`, `animatorOffset`, `rangeStart`, `rangeEnd`).
119
+ - `ease` (string, default `"smoothstep"`): GSAP-style easing curve (`"power1"`, `"power2"`, `"power3"`, `"power4"`, `"sine"`, `"expo"`, `"back"`, `"smoothstep"`, `"linear"`).
120
+ - `transform`: Property offsets applied to selected glyph units:
121
+ - `deltaX` / `deltaY` (number): Spatial displacement in pixels.
122
+ - `deltaRotation` (number, degrees): 2D in-plane rotation angle.
123
+ - `deltaScale` (number, relative multiplier, e.g. -1 for full collapse to 0).
124
+ - `scaleX` / `scaleY` (number, directional scale multiplier).
125
+ - `rotationX` (number, degrees): 3D perspective flip squashing around the local glyph baseline.
126
+ - `opacity` (number, absolute or relative alpha override).
127
+ - `blur` (number): Gaussian blur radius.
128
+ - **`shape`**: Parametric vector shape and path primitive for motion graphics.
129
+ - `shapeType` (`"rect"` | `"circle"` | `"ellipse"` | `"polygon"` | `"star"` | `"arrow"` | `"path"`, default `"rect"`).
130
+ - Geometry: `borderRadius`, `radiusTL`, `radiusTR`, `radiusBR`, `radiusBL`, `polygonSides` (3..64), `starPoints` (3..64), `starInnerRadiusRatio` (0.01..0.99), `arrowHeadWidth`, `arrowHeadLength`, `arrowShaftWidth`, `d` (SVG path data: `M L H V C S Q T A Z`, absolute and relative; arcs included).
131
+ - Fill: `fillType` (`"solid"` | `"linear"` | `"radial"` | `"none"`), `fillColor`, `gradientEndColor`, `gradientAngle`.
132
+ - Stroke: `strokeColor`, `strokeWidth`, `strokeDashArray`, `strokeDashOffset`, `strokeLineCap` (`"butt"` | `"round"` | `"square"`), `strokeLineJoin` (`"miter"` | `"round"` | `"bevel"`), `strokeAlign` (`"inside"` | `"center"` | `"outside"`).
133
+ - Trim Paths: `trimStart` (0..1), `trimEnd` (0..1), `trimOffset` (rotation/offset).
134
+ - **Charts**: there is no chart node kind. `Layer.chart` in the `framefields` SDK expands a chart into `box`, `shape` (`"path"`/`"circle"`) and `text` nodes, so a program document describes a chart with those kinds.
135
+ - **`media`**: `inputHandleId` (string, required — must match a connected input handle).
136
+ - **Standard Media (`Video`, `Image`, `GIF`, `SVG`, `Lottie`)**: `fit` (`"cover"` | `"contain"` | `"fill"` | `"none"`, default `"contain"`), `volume` (0–1), `muted`, `borderRadius`, `borderColor`, `borderWidth`.
137
+ - **Captions & Subtitles (`Caption` DataType)**: When bound to a `Caption` input (SRT source), `kind: "media"` renders time-synchronized subtitle cues:
138
+ - `fontSize` (number, default `48`): Font size in pixels.
139
+ - `fontFamily` (string, default `"Inter"`): Font family.
140
+ - `fontWeight` (string | number, default `700`): Weight (`"normal"`, `"bold"`, `400`, `700`, `900`).
141
+ - `fontStyle` (`"normal"` | `"italic"`).
142
+ - `fill` (string, default `"#ffffff"`): Text color.
143
+ - `align` (`"start"` | `"center"` | `"end"`, default `"center"`): Horizontal text alignment.
144
+ - `verticalAlign` (`"top"` | `"middle"` | `"bottom"`, default `"bottom"`): Vertical alignment. When `"bottom"`, the bottom edge is anchored, and multi-line subtitle cues expand **upwards**.
145
+ - `lineHeight` (number, default `1.2` or `fontSize * 1.2`).
146
+ - `letterSpacing` (number, default `0`).
147
+ - `background` (string, optional): Background box fill color behind the active subtitle text.
148
+ - `padding` (number, optional): Inset padding around text.
149
+ - `borderRadius` / `strokeRadius` (number, default `8`): Corner radius for background rectangle.
150
+ - `stroke` (string) / `strokeWidth` (number): Text outline stroke.
151
+ - `textShadow` / `shadows`: Drop shadows.
152
+ - **Sizing & Placement**: An explicit `width` (e.g. `800` or `"80%"`) controls word-wrapping width. When `height` is omitted or `"auto"`, the engine measures the maximum height needed across all cues in the SRT file to keep layout and positioning stable throughout playback.
153
+
154
+ Layout semantics (HTML-like):
155
+ - A **flex** node with `dir: "column"` stacks children vertically; `dir: "row"` lays them horizontally.
156
+ - **block** behaves as a column container whose width fills the parent.
157
+ - **box** without children is a styled rectangle (fill + radius); with children it wraps them in a column.
158
+ - `"fill"`/`"fit"` sizes resolve against the containing block; `grow` splits leftover space.
159
+ - **absolute** nodes are removed from flow and placed at `x`/`y` of their parent's content box.
160
+ - **Text wraps**: an explicit numeric `width` wraps at that width. The node box always matches the drawn (wrapped) text.
161
+ - **Captions**: Render synchronized cues from connected SRT sources. Default to bottom alignment (`verticalAlign: "bottom"`), expanding earlier lines upwards when wrapping across multiple lines.
162
+ - **Canvas bounds**: the output is exactly `width`×`height`. Nodes may extend beyond it (large sizes, negative `x`/`y`) — anything outside the canvas is clipped in the output. Use `fit`/`contain`/`fill` and canvas-sized boxes for fully-visible media.
163
+
164
+ ---
165
+
166
+ ### Animation Schema (per node)
167
+ - **`tracks`** (array): Up to 24 animation tracks.
168
+ Each track represents animatable property modifications:
169
+ - **`id`** (string, required): Unique identifier for the track.
170
+ - **`prop`** (string, enum, required): `x`, `y`, `scale`, `rotation`, `opacity`, `fill`, `color`, `letterSpacing`, `width`, `height`, `volume`, `hidden`, `muted`, `fontSize`, `text`, `trimStart`, `trimEnd`, `trimOffset`, `strokeWidth`, `strokeDashOffset`, `cornerRadius`, `starInnerRadiusRatio`, `fillColor`, `strokeColor`, `rangeStart`, `rangeEnd`, `offset`, `animatorRangeStart`, `animatorRangeEnd`, `animatorOffset`, `rotateX`, `rotateY`, `rotateZ`, `perspective`, `translateZ`, `perspectiveOriginX`, `perspectiveOriginY`, `progress`, `drawProgress`, `chartProgress`.
171
+ - **`source`** (object, optional, default `{ type: "keyframe" }`): Driver evaluating this track.
172
+ - `{ type: "keyframe" }`: Keyframe sequence (requires `keyframes`).
173
+ - `{ type: "signal", inputHandleId: string, multiplier?: number, offset?: number, smoothingWindowFrames?: number, signalMode?: "continuous" | "accumulate", threshold?: number, debounceFrames?: number, colorMode?: "interpolate" | "hueRotate" | "threshold", colorA?: string, colorB?: string, colorThreshold?: number }`: Samples a connected `Signal` variable input handle (e.g. from `AudioSignalExtractor` or `SignalMath`). In `"continuous"` mode, maps amplitude or continuous values directly. In `"accumulate"` mode, acts as a discrete event integrator that detects rising-edge acoustic transients/peaks exceeding `threshold` (with `debounceFrames` guard) and increments an integer step count on each peak — ideal for audio-reactive typography where each keystroke/drum hit reveals letters sequentially. Also supports dynamic color properties (`fill`, `color`) via linear interpolation, hue rotation, or binary threshold switching. Aliases `handleId` (for `inputHandleId`), `amplitude` (for `multiplier`), and `smoothing` (for `smoothingWindowFrames`) are also accepted. For procedural drivers like `signal`, `keyframes: []` can be empty.
174
+ - `{ type: "wiggle", frequency: number, octaves?: number, amplitude: number, offset?: number, seed?: number }`: Multi-octave continuous coherent gradient noise.
175
+ - `{ type: "springOvershoot", mass?: number, stiffness?: number, damping?: number, initialVelocity?: number, targetValue?: number }`: Analytical closed-form damped harmonic oscillator ODE.
176
+ - **`keyframes`** (array, optional when procedural `source` is used): Chronologically sorted keyframe points.
177
+ - **`repeat`** (number, optional): GSAP loop count (e.g., -1 for infinite loops).
178
+ - **`yoyo`** (boolean, optional): If true, animates back and forth.
179
+
180
+ #### Keyframe Schema:
181
+ - **`id`** (string, required): Unique keyframe identifier.
182
+ - **`frame`** (number, required): Clip-relative frame number where this keyframe is reached (`0` = node start).
183
+ - **`value`** (number or boolean, required): Target value at this keyframe.
184
+ - **`ease`** (object, optional): Segment easing parameters.
185
+ - **`name`**: `none`, `power1`, `power2`, `power3`, `sine`, `circ`, `expo`, `back`, `elastic`, `bounce`, `spring`, `cubic`, `hold`.
186
+ - **`dir`**: `in`, `out`, `inOut`.
187
+ - **`params`** (array of numbers): Optional easing parameter overrides (e.g. `back.out(1.7)`).
188
+ - **`cubicParams`** (array of 4 numbers, optional): Control points `[x1, y1, x2, y2]` when `name: "cubic"`.
189
+ - **`spatialTangentIn`** (array of 2 numbers `[dx, dy]`, optional): Ingoing spatial tangent handle for 2D position spline trajectories.
190
+ - **`spatialTangentOut`** (array of 2 numbers `[dx, dy]`, optional): Outgoing spatial tangent handle for 2D position spline trajectories.
191
+
192
+ ---
193
+
194
+ ### Ordering / Z-Index
195
+ - Within each parent level, nodes draw in ascending `zIndex` (default `0`).
196
+ - The tree order (children array order) is the layout order; `zIndex` only breaks ties within a level.
197
+
198
+ ## Output
199
+ | Handle | Type | Description |
200
+ |--------|------|-------------|
201
+ | Result | Image, Video | The final rendered composite media file. |
202
+
203
+ ## Common Patterns
204
+ - **Title card:** one `flex` column (`align: "center"`, `pad`, `fill`) containing title `text`, subtitle `text`, and a `flex` row of `box` chips with `media` avatars. Animate the column's `opacity`, the row's `y`, and a media node's `scale` with keyframe tracks.
205
+ - **Video Subtitles / Captions:** Add a dynamic `Caption` input handle (e.g. `"subtitles"`). In the layout tree, place a `media` node bound to `"subtitles"` with `width: 900`, `fill: "#ffffff"`, `fontSize: 44`, `align: "center"`, `verticalAlign: "bottom"`, and place it near the bottom of the canvas (e.g. `x: 90`, `y: 880` or in a bottom-aligned flex container).
206
+ - **Watermarking a Video:** a `flex` row (`align: "end"`, `justify: "end"`, full canvas) containing a `media` node bound to the PNG input; low `opacity`.
207
+ - **Picture-in-Picture:** a `flex` row with two `media` nodes (`fit: "cover"`, each `grow: 1`).
208
+ - **Audio-Reactive 3D Models:** a `kind: "model3d"` node loading an OBJ/FBX/GLTF/STL/PLY mesh with `audioDeform: { audioTrackId, mode: "normal_extrusion" | "radial_pulse" | "harmonic_wave" | "twist" | "ripple", amplitudeMultiplier, damping }`, deformed directly in VRAM via WebGPU compute shaders driven by audio frequency spectra and transient energies. `twist` turns each slice about Y by its height (harder on the bass); `ripple` runs concentric waves across XY, struck by the drums. `audioTrackId` may be an audio layer's id or an audio file path.
209
+ - **glTF scenes:** a glTF/GLB `model3d` keeps its node hierarchy, every skin and its animation clips (translation, rotation, scale; linear, step and cubic-spline). Skinned meshes are posed per frame from the clip (`animationName`, `animationTime`/`animationProgress`, `loop`); base-colour textures (with mipmaps), alpha `MASK`/`BLEND` and VRM MToon materials (`material: "toon"`, picked automatically when `material` is unset) render as authored. `center`/`normalizeSize` apply above the scene graph, so skinning stays intact.
210
+
211
+ ## Don't Forget
212
+ - If text rendering required, you must include a font in spec (CLI TOOL only). By default emoji font is not loaded. NotoColorEmoji seems to be working well. Try to use NotoColorEmoji as default font for emojis.
213
+ - The `layout` tree IS the composition — there is no other layer model. Every **media** node
214
+ needs a valid `inputHandleId` matching a connected input, and every node needs a `kind`.
215
+ - `type` is an input DataType — never put it on layout nodes; use `kind`.
216
+ - Connected inputs do NOT render automatically — build the tree explicitly.
217
+ - **Static Image Compositions:** When outputting static ad banners, posters, or graphics, set `"mode": "Image"` and ensure all connected filter nodes (such as `FilmGrain`) are configured statically (`animated: false`). Do NOT insert `ExtractFrame` nodes for static image compositions — connect `Compositor` directly to `Export`.
218
+
219
+ ## Live Canvas Authoring vs. Offline CLI/Spec Compilation
220
+ Understanding how `inputHandleId` resolves is critical depending on the authoring environment:
221
+
222
+ - **Live Canvas Authoring:**
223
+ - You must use the **actual dynamic input handle ID** returned after creating/inspecting the Compositor node (e.g. `input_abc123`).
224
+ - **Never use handle labels here.** Live runtime lookups query inputs directly by their exact handle IDs.
225
+ - **Offline CLI / Spec Compilation:**
226
+ - A **human-readable handle label** (e.g. `"bg_canvas_handle"`) may be used in `inputHandleId`.
227
+ - The Artifex runner automatically resolves and maps these human-readable labels to internal generated IDs (`temp-xxxx`) during graph compilation.
228
+ - This mapping is recursively applied to matching dynamic input labels in root configuration properties and Compositor `layout` tree elements (`inputHandleId`).
229
+
230
+ > [!WARNING]
231
+ > **Live Canvas Warning:** Do not place a handle label in `inputHandleId` when updating an existing live Compositor. The renderer may treat the media as unbound and produce a blank layer.
232
+
233
+ ---
234
+
235
+ ## Verification Checklist
236
+ When authoring or modifying a Compositor configuration, verify:
237
+ 1. **Confirm the graph edge exists:** The upstream source node is connected to the corresponding dynamic input handle on the Compositor.
238
+ 2. **Confirm the layout contains a media node:** A `kind: "media"` (or `kind: "text"`) node is explicitly declared in `config.layout`.
239
+ 3. **Confirm its binding matches the live Compositor input:** The `inputHandleId` matches the live dynamic input handle ID (or human-readable handle label if building an offline CLI spec).
240
+ 4. **Preview a frame where opacity is greater than zero:** Check an active playback frame where `opacity > 0`, `hidden` is not `true`, and the frame falls within `startFrame` and `durationFrames`.
241
+
242
+ ---
243
+
244
+ ## Troubleshooting: Blank Output or Missing Layers
245
+ If a layer or the final composite renders blank, check the following common failure modes:
246
+
247
+ - **Missing media node:** The upstream node is connected in the graph canvas, but no `kind: "media"` element exists in `config.layout`. Inputs do not render automatically without a layout node.
248
+ - **Stale or incorrect input binding:** `inputHandleId` references a non-existent handle, uses a human-readable label in live canvas mode instead of the real handle ID, or refers to a deleted/renamed handle. The renderer treats unbound media as empty.
249
+ - **Zero opacity:** The node's `opacity` is `0`, a parent container's `opacity` is `0`, or an animation track interpolates `opacity` to `0` at the inspected frame.
250
+ - **Timeline outside `startFrame`/`durationFrames`:** The current frame is outside the active clip range (`frame < startFrame` or `frame >= startFrame + durationFrames`).
251
+ - **Layer outside canvas bounds:** Absolute coordinates (`x`, `y`), padding/offsets, or transforms place the layer completely outside the canvas `width`×`height`, or `scale` is `0`.
252
+ - **Hidden node or invalid dimensions:** `hidden: true` is set, or `width`/`height` evaluates to `0` with no intrinsic media dimensions available.
253
+
254
+ ---
255
+
256
+ ## Example JSON Configuration
257
+ ```json
258
+ {
259
+ "width": 1920,
260
+ "height": 1080,
261
+ "backgroundColor": "#16130d",
262
+ "fps": 24,
263
+ "mode": "Video",
264
+ "layout": [
265
+ {
266
+ "id": "hero",
267
+ "kind": "flex",
268
+ "dir": "column",
269
+ "gap": 24,
270
+ "padding": 80,
271
+ "align": "center",
272
+ "width": "fill",
273
+ "height": "fill",
274
+ "animation": {
275
+ "tracks": [
276
+ {
277
+ "id": "hero-fade",
278
+ "prop": "opacity",
279
+ "keyframes": [
280
+ { "id": "kf0", "frame": 0, "value": 0 },
281
+ { "id": "kf1", "frame": 15, "value": 1, "ease": { "name": "power2", "dir": "out" } }
282
+ ]
283
+ }
284
+ ]
285
+ },
286
+ "children": [
287
+ {
288
+ "id": "title",
289
+ "kind": "text",
290
+ "text": "Big Title",
291
+ "fontSize": 96,
292
+ "fontWeight": 900,
293
+ "fill": "#f4ead8"
294
+ },
295
+ {
296
+ "id": "subtitle",
297
+ "kind": "text",
298
+ "text": "Rendered by the compositor layout engine",
299
+ "fontSize": 40,
300
+ "fill": "#b8a88a"
301
+ },
302
+ {
303
+ "id": "badges",
304
+ "kind": "flex",
305
+ "dir": "row",
306
+ "gap": 16,
307
+ "animation": {
308
+ "tracks": [
309
+ {
310
+ "id": "badges-rise",
311
+ "prop": "y",
312
+ "keyframes": [
313
+ { "id": "kf0", "frame": 0, "value": 40 },
314
+ { "id": "kf1", "frame": 20, "value": 0, "ease": { "name": "back", "dir": "out" } }
315
+ ]
316
+ }
317
+ ]
318
+ },
319
+ "children": [
320
+ { "id": "chip-avatar", "kind": "box", "width": 160, "height": 48, "borderRadius": 24, "background": "#3a2f1e" },
321
+ { "id": "chip-hero", "kind": "box", "width": 160, "height": 48, "borderRadius": 24, "background": "#3a2f1e" }
322
+ ]
323
+ }
324
+ ]
325
+ },
326
+ {
327
+ "id": "avatar-img",
328
+ "kind": "media",
329
+ "inputHandleId": "avatar",
330
+ "fit": "cover",
331
+ "width": 200,
332
+ "height": 200,
333
+ "borderRadius": 100,
334
+ "startFrame": 0,
335
+ "durationFrames": 72,
336
+ "animation": {
337
+ "tracks": [
338
+ {
339
+ "id": "avatar-pop",
340
+ "prop": "scale",
341
+ "keyframes": [
342
+ { "id": "kf0", "frame": 0, "value": 1.15 },
343
+ { "id": "kf1", "frame": 30, "value": 1 }
344
+ ]
345
+ }
346
+ ]
347
+ }
348
+ },
349
+ {
350
+ "id": "subtitles",
351
+ "kind": "media",
352
+ "inputHandleId": "subtitles_handle",
353
+ "position": "absolute",
354
+ "x": 160,
355
+ "y": 860,
356
+ "width": 1600,
357
+ "fontSize": 48,
358
+ "fontWeight": 700,
359
+ "fill": "#ffffff",
360
+ "align": "center",
361
+ "verticalAlign": "bottom",
362
+ "stroke": "#000000",
363
+ "strokeWidth": 4,
364
+ "background": "#00000088",
365
+ "padding": 16,
366
+ "borderRadius": 12
367
+ }
368
+ ]
369
+ }
370
+ ```
@@ -0,0 +1,139 @@
1
+ import "@framefields/node-sdk";
2
+ import * as _framefields_node_sdk_renderer0 from "@framefields/node-sdk/renderer";
3
+
4
+ //#region src/renderers/transform3d.d.ts
5
+ /**
6
+ * Pure 4x4 matrix and perspective projection mathematics for 3D layout containers.
7
+ * Column-major 4x4 representation matching WebGPU standard conventions.
8
+ */
9
+ type Matrix4x4 = [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number];
10
+ interface Point2D {
11
+ x: number;
12
+ y: number;
13
+ }
14
+ interface Point3D {
15
+ x: number;
16
+ y: number;
17
+ z: number;
18
+ }
19
+ interface Quad2D {
20
+ topLeft: Point2D;
21
+ topRight: Point2D;
22
+ bottomLeft: Point2D;
23
+ bottomRight: Point2D;
24
+ }
25
+ interface Layer3DMatrixConfig {
26
+ x: number;
27
+ y: number;
28
+ width: number;
29
+ height: number;
30
+ rotateXDeg?: number;
31
+ rotateYDeg?: number;
32
+ rotateZDeg?: number;
33
+ scaleX?: number;
34
+ scaleY?: number;
35
+ perspectivePx?: number;
36
+ originXRatio?: number;
37
+ originYRatio?: number;
38
+ translateZPx?: number;
39
+ }
40
+ declare const Transform3DMath: {
41
+ /**
42
+ * Creates a 4x4 identity matrix.
43
+ */
44
+ identity(): Matrix4x4;
45
+ /**
46
+ * Multiplies two 4x4 matrices: out = A x B.
47
+ */
48
+ multiply(a: Matrix4x4, b: Matrix4x4): Matrix4x4;
49
+ /**
50
+ * Translation matrix in 3D.
51
+ */
52
+ translate(tx: number, ty: number, tz?: number): Matrix4x4;
53
+ /**
54
+ * Scale matrix in 3D.
55
+ */
56
+ scale(sx: number, sy: number, sz?: number): Matrix4x4;
57
+ /**
58
+ * Rotation around X axis (pitch) in radians.
59
+ */
60
+ rotateX(rad: number): Matrix4x4;
61
+ /**
62
+ * Rotation around Y axis (yaw) in radians.
63
+ */
64
+ rotateY(rad: number): Matrix4x4;
65
+ /**
66
+ * Rotation around Z axis (roll) in radians.
67
+ */
68
+ rotateZ(rad: number): Matrix4x4;
69
+ /**
70
+ * Perspective projection matrix.
71
+ * If `d <= 0`, returns identity (orthographic projection).
72
+ * In standard CSS/computer graphics coordinate system:
73
+ * M[11] = -1 / d (in column-major index 11 is row 3, col 2).
74
+ */
75
+ perspective(d: number): Matrix4x4;
76
+ /**
77
+ * Computes the complete 3D composite transform for a layer.
78
+ *
79
+ * Order of operations:
80
+ * 1. Move origin to perspective origin / pivot anchor.
81
+ * 2. Apply perspective projection matrix P(d).
82
+ * 3. Apply translation (translateZ).
83
+ * 4. Apply 3D rotations: RotateZ * RotateY * RotateX (Euler angle order).
84
+ * 5. Move origin back from pivot anchor.
85
+ * 6. Apply scale (scaleX, scaleY).
86
+ */
87
+ buildLayer3DMatrix(config: Layer3DMatrixConfig): Matrix4x4;
88
+ /**
89
+ * Projects a 3D point (x, y, z, 1) through a 4x4 matrix, including perspective division by W.
90
+ */
91
+ projectPoint(m: Matrix4x4, p: Point3D): Point2D;
92
+ /**
93
+ * Projects the 4 corner vertices of a flat 2D rectangle (0,0, w, h) through the 3D matrix.
94
+ * Produces the target Quad2D for the WebGPU homography/corner-pin renderer.
95
+ */
96
+ projectRectangleCorners(matrix: Matrix4x4, width: number, height: number): Quad2D;
97
+ /**
98
+ * Computes the signed area of the projected quad to check whether the layer is facing away
99
+ * (backface culling).
100
+ * If signedArea < 0 in screen space (y-down), the surface normal points away from the camera.
101
+ */
102
+ computeSignedArea(quad: Quad2D): number;
103
+ };
104
+ /**
105
+ * Computes an 8-parameter 3x3 homography matrix mapping destination coordinates (quad)
106
+ * to normalized source coordinates (unit square [0, 1]^2).
107
+ *
108
+ * The homography H maps destination (x, y) to source (u, v):
109
+ * u = (h00*x + h01*y + h02) / (h20*x + h21*y + h22)
110
+ * v = (h10*x + h11*y + h12) / (h20*x + h21*y + h22)
111
+ *
112
+ * Resulting array: [h00, h01, h02, h10, h11, h12, h20, h21, 1.0]
113
+ */
114
+ declare function solveHomography(dstPoints: Point2D[], srcPoints?: Point2D[]): number[];
115
+ interface Transform3DPropsLike {
116
+ rotateX?: number;
117
+ rotateY?: number;
118
+ rotateZ?: number;
119
+ translateZ?: number;
120
+ perspective?: number;
121
+ perspectiveOriginX?: number;
122
+ perspectiveOriginY?: number;
123
+ animation?: {
124
+ tracks?: Array<{
125
+ prop: string;
126
+ }>;
127
+ };
128
+ [key: string]: unknown;
129
+ }
130
+ /**
131
+ * Checks whether a layer operation or compiled stub activates the 3D perspective path.
132
+ */
133
+ declare function has3DTransform(target?: Transform3DPropsLike, lop?: Transform3DPropsLike): boolean;
134
+ //#endregion
135
+ //#region src/renderers/index.d.ts
136
+ declare const _default: Readonly<_framefields_node_sdk_renderer0.NodeRendererPlugin>;
137
+ //#endregion
138
+ export { Point3D as a, Transform3DPropsLike as c, Point2D as i, has3DTransform as l, Layer3DMatrixConfig as n, Quad2D as o, Matrix4x4 as r, Transform3DMath as s, _default as t, solveHomography as u };
139
+ //# sourceMappingURL=index-B0ir9Z7o.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-B0ir9Z7o.d.mts","names":[],"sources":["../src/renderers/transform3d.ts","../src/renderers/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;AAKY,KAAA,SAAA,GAAS,CAmBJ,MAAA,EAKA,MAAA,EAMA,MAAA,EACP,MAAA,EACC,MAAA,EACE,MAAA,EACC,MAAA,EAAO,MAAA,EAGJ,MAAA,EAgBJ,MAAA,EAIA,MAAA,EAOA,MAAA,EAAc,MAAA,EAAY,MAAA,EAiBK,MAAA,EAOJ,MAAA,CAOjB;AASA,UArFN,OAAA,CAqFM;EASA,CAAA,EAAA,MAAA;EAYE,CAAA,EAAA,MAAA;;AAkByB,UAvHjC,OAAA,CAuHiC;EAgFjC,CAAA,EAAA,MAAA;EAAc,CAAA,EAAA,MAAA;EAAU,CAAA,EAAA,MAAA;;AAwBrC,UAzNa,MAAA,CAyNb;EAsBqB,OAAA,EA9Of,OA8Oe;EAAM,QAAA,EA7OpB,OA6OoB;EAuBf,UAAA,EAnQH,OAmQkB;EAyEd,WAAA,EA3UH,OA2UG;AAejB;UAvViB,mBAAA;;;ECxCa,KAAA,EAAA,MAAA;;;;;;;;;;;;cDwDjB;;;;cAIA;;;;cAOA,cAAc,YAAY;;;;kDAiBK;;;;8CAOJ;;;;wBAOjB;;;;wBASA;;;;wBASA;;;;;;;0BAYE;;;;;;;;;;;;6BAkBG,sBAAsB;;;;kBAgFjC,cAAc,UAAU;;;;;kCAqB/B,2CAGN;;;;;;0BAsBqB;;;;;;;;;;;;iBAuBT,eAAA,YACJ,uBACA;UAuEK,oBAAA;;;;;;;;;aAQO;;;;;;;;;iBAOR,cAAA,UACN,4BACH;;;cCjYuB,UAAA,SAAA,+BAAA,CAAA,kBAAA"}