@depths/waves 0.1.0 → 0.2.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.
- package/README.md +2040 -300
- package/dist/chunk-7QPNRHMW.mjs +484 -0
- package/dist/chunk-PKLHVWMD.mjs +3158 -0
- package/dist/cli.js +3212 -180
- package/dist/cli.mjs +132 -56
- package/dist/index.d.mts +27 -12
- package/dist/index.d.ts +27 -12
- package/dist/index.js +3140 -191
- package/dist/index.mjs +3 -5
- package/dist/registry-C6H9G0df.d.mts +204 -0
- package/dist/registry-C6H9G0df.d.ts +204 -0
- package/dist/remotion/index.d.mts +3 -3
- package/dist/remotion/index.d.ts +3 -3
- package/dist/remotion/index.js +2860 -106
- package/dist/remotion/index.mjs +3 -6
- package/package.json +19 -19
- package/dist/chunk-TGAL5RQN.mjs +0 -404
- package/dist/chunk-WGQITADJ.mjs +0 -284
- package/dist/registry-hVIyqwS6.d.mts +0 -355
- package/dist/registry-hVIyqwS6.d.ts +0 -355
package/README.md
CHANGED
|
@@ -1,41 +1,140 @@
|
|
|
1
|
-
# @depths/waves
|
|
1
|
+
# @depths/waves (v0.2.0)
|
|
2
2
|
|
|
3
|
-
`@depths/waves` is a TypeScript-first library for rendering videos from a JSON
|
|
3
|
+
`@depths/waves` is a TypeScript-first library + CLI for rendering videos from a JSON "intermediate representation" (IR) using Remotion.
|
|
4
4
|
|
|
5
5
|
The intended workflow is:
|
|
6
6
|
|
|
7
7
|
1. An LLM (or a human) produces JSON that conforms to a schema (`VideoIRSchema`).
|
|
8
|
-
2. The IR is validated (schema + semantics).
|
|
8
|
+
2. The IR is validated (schema + semantics + registry contracts).
|
|
9
9
|
3. The IR is rendered to an output video file (MP4 by default) using Remotion.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
v0.2.0 adds a shadcn-like catalog of higher-level "composite" components and a hybrid IR that supports:
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- Scene timing is validated for common authoring mistakes (gaps, overlaps, duration mismatches, children exceeding parent bounds).
|
|
13
|
+
- **Segments (recommended for agents):** sequential scenes with optional overlaps ("transitions")
|
|
14
|
+
- **Timeline (escape hatch):** explicit timed nodes with overlaps allowed
|
|
16
15
|
|
|
17
|
-
##
|
|
16
|
+
## Table of contents
|
|
18
17
|
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
18
|
+
- [Concept (end-to-end)](#concept-end-to-end)
|
|
19
|
+
- [What problem this solves](#what-problem-this-solves)
|
|
20
|
+
- [Mental model](#mental-model)
|
|
21
|
+
- [Core building blocks](#core-building-blocks)
|
|
22
|
+
- [How segments + transitions work](#how-segments--transitions-work)
|
|
23
|
+
- [Validation model](#validation-model)
|
|
24
|
+
- [Rendering model](#rendering-model)
|
|
25
|
+
- [Installation](#installation)
|
|
26
|
+
- [CLI (agent workflow)](#cli-agent-workflow)
|
|
27
|
+
- [IR v2.0 (authoring contract)](#ir-v20-authoring-contract)
|
|
28
|
+
- [Components (primitives + composites)](#components-primitives--composites)
|
|
29
|
+
- [Examples (composition recipes)](#examples-composition-recipes)
|
|
30
|
+
- [Library API (quickstart)](#library-api-quickstart)
|
|
31
|
+
- [Assets and paths (Windows/Linux)](#assets-and-paths-windowslinux)
|
|
32
|
+
- [Rendering prerequisites](#rendering-prerequisites)
|
|
33
|
+
- [Local CLI testing (before publishing)](#local-cli-testing-before-publishing)
|
|
34
|
+
- [Contributing](#contributing)
|
|
25
35
|
|
|
26
|
-
##
|
|
36
|
+
## Concept (end-to-end)
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
### What problem this solves
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
- The rendering system has hidden magic (implicit component inference, brittle conventions).
|
|
32
|
-
- The model lacks a precise schema + component catalog to target.
|
|
40
|
+
Remotion is a powerful way to render videos by writing React components. But for "LLM -> video" workflows, you need more than React:
|
|
33
41
|
|
|
34
|
-
|
|
42
|
+
- A strict authoring contract (so the model can reliably output something valid)
|
|
43
|
+
- Fast, deterministic validation (so failures happen before render time)
|
|
44
|
+
- A component catalog (so the model composes using known building blocks)
|
|
45
|
+
- A CLI (so an agent can drive the entire pipeline from a terminal)
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
Waves is a thin, explicit layer over Remotion that provides exactly those pieces.
|
|
48
|
+
|
|
49
|
+
### Mental model
|
|
50
|
+
|
|
51
|
+
Think of Waves as a compiler + runtime:
|
|
52
|
+
|
|
53
|
+
1) **Author**: you (or an agent) author a JSON IR document
|
|
54
|
+
2) **Validate**: Waves validates schema + semantics + component props
|
|
55
|
+
3) **Compile**: Waves compiles high-level authoring (segments) into a render-ready timeline
|
|
56
|
+
4) **Render**: Waves invokes Remotion bundling + rendering to produce an MP4
|
|
57
|
+
|
|
58
|
+
At render time, Waves is "just React + Remotion". The strictness lives earlier (IR + validation + registry contracts).
|
|
59
|
+
|
|
60
|
+
### Core building blocks
|
|
61
|
+
|
|
62
|
+
The codebase has a small number of core concepts:
|
|
63
|
+
|
|
64
|
+
- **Video IR (`VideoIRSchema`)**: the JSON document format you author.
|
|
65
|
+
- v0.2.0 targets `version: "2.0"` only.
|
|
66
|
+
- You author exactly one of `segments[]` (recommended) or `timeline[]` (escape hatch).
|
|
67
|
+
- **Component Registry (`ComponentRegistry`)**: a map from string `type` -> React component + Zod props schema + metadata.
|
|
68
|
+
- Only registered components can render.
|
|
69
|
+
- Props are validated with Zod and defaults are applied deterministically.
|
|
70
|
+
- **Validator (`IRValidator`)**: validates:
|
|
71
|
+
- IR schema (Zod)
|
|
72
|
+
- semantic rules (timing bounds, duration math, transition overlap constraints)
|
|
73
|
+
- registry contracts (type exists, props valid, children allowed, etc.)
|
|
74
|
+
- **Compiler (`compileToRenderGraph`)**: compiles authored IR into a normalized, timed representation used by rendering.
|
|
75
|
+
- In segments mode: inserts an internal `Segment` wrapper node for each segment and computes overlaps.
|
|
76
|
+
- In timeline mode: fills missing `timing` defaults (whole-video / whole-parent).
|
|
77
|
+
- **Renderer (`WavesEngine`)**: bundles a temporary Remotion project and calls Remotion's renderer.
|
|
78
|
+
- It writes a temporary entrypoint, bundles, selects a composition, and renders media.
|
|
79
|
+
|
|
80
|
+
### How segments + transitions work
|
|
81
|
+
|
|
82
|
+
Segments are the recommended authoring style for agents because they reduce timing math:
|
|
83
|
+
|
|
84
|
+
- Each segment has `durationInFrames` and a `root` node.
|
|
85
|
+
- Segment start times are derived by order.
|
|
86
|
+
- Optional overlaps are declared via `transitionToNext`.
|
|
87
|
+
|
|
88
|
+
Waves compiles segments into a "render timeline" (a list of timed component nodes). Each segment becomes:
|
|
89
|
+
|
|
90
|
+
- a root `Segment` node (internal; not shown in the LLM catalog)
|
|
91
|
+
- whose child is the authored `root` node
|
|
92
|
+
|
|
93
|
+
Why the internal `Segment` wrapper exists:
|
|
94
|
+
|
|
95
|
+
- Overlap transitions are easiest to implement at the segment boundary.
|
|
96
|
+
- The wrapper can apply fade/slide/zoom/clip effects for the first/last frames of a segment.
|
|
97
|
+
- The wrapper receives both the "enter transition" (inferred from the previous segment's `transitionToNext`) and the "exit transition" (the current segment's `transitionToNext`).
|
|
98
|
+
|
|
99
|
+
Overlap math:
|
|
100
|
+
|
|
101
|
+
- Let segment `i` have duration `Di`.
|
|
102
|
+
- Let overlap `Oi` be `segments[i].transitionToNext.durationInFrames` (only for segments that have a next segment).
|
|
103
|
+
- Then the compiled start time is:
|
|
104
|
+
- `start(0) = 0`
|
|
105
|
+
- `start(i+1) = start(i) + Di - Oi`
|
|
106
|
+
- The compiled video end must equal `video.durationInFrames`.
|
|
107
|
+
|
|
108
|
+
### Validation model
|
|
109
|
+
|
|
110
|
+
Waves tries to fail early with errors that are usable for an agent:
|
|
111
|
+
|
|
112
|
+
1) **Schema validation** (Zod):
|
|
113
|
+
- ensures the IR has the expected structure (types, required fields, etc.)
|
|
114
|
+
2) **Semantic validation**:
|
|
115
|
+
- validates segment overlap constraints (overlap cannot exceed either segment)
|
|
116
|
+
- validates that all timed nodes stay within their parent duration
|
|
117
|
+
- validates that the compiled timeline end equals `video.durationInFrames`
|
|
118
|
+
3) **Registry validation**:
|
|
119
|
+
- unknown component types fail
|
|
120
|
+
- props fail if they don't match the component's Zod schema
|
|
121
|
+
- children fail if the component metadata forbids children (or requires a certain number)
|
|
122
|
+
|
|
123
|
+
The validator applies Zod defaults into the IR node objects. This is intentional: it makes render-time props deterministic and reduces "undefined" cases inside components.
|
|
124
|
+
|
|
125
|
+
### Rendering model
|
|
126
|
+
|
|
127
|
+
Rendering is done by generating a tiny Remotion project on the fly:
|
|
128
|
+
|
|
129
|
+
1) Waves validates + compiles the IR to a timed timeline.
|
|
130
|
+
2) Waves writes a temporary `entry.tsx` that:
|
|
131
|
+
- registers the `WavesComposition` as the Remotion root
|
|
132
|
+
- embeds the compiled IR as JSON
|
|
133
|
+
- registers built-in components (and optional user `--register` modules)
|
|
134
|
+
3) Waves calls `@remotion/bundler` to create a bundle.
|
|
135
|
+
4) Waves calls `@remotion/renderer` to select the composition and render media.
|
|
136
|
+
|
|
137
|
+
This means you do not need to maintain a separate Remotion project just to use Waves.
|
|
39
138
|
|
|
40
139
|
## Installation
|
|
41
140
|
|
|
@@ -49,387 +148,2028 @@ Peer dependencies (must be installed by the consumer):
|
|
|
49
148
|
npm i react remotion
|
|
50
149
|
```
|
|
51
150
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- `ffmpeg` must be installed and available on the PATH.
|
|
55
|
-
- A Chromium browser must be available to `@remotion/renderer` (Remotion’s renderer runs headless Chromium).
|
|
56
|
-
|
|
57
|
-
## CLI / Agent workflow
|
|
58
|
-
|
|
59
|
-
Waves ships a `waves` CLI so a locally running AI agent (or any terminal user) can:
|
|
60
|
-
|
|
61
|
-
1) fetch a system prompt + JSON Schemas (the authoring contract)
|
|
62
|
-
2) write a starter IR file
|
|
63
|
-
3) validate the IR
|
|
64
|
-
4) render an MP4 from the IR
|
|
65
|
-
|
|
66
|
-
Install locally (recommended):
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
npm i -D @depths/waves
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Run the CLI via the local bin:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npx waves --help
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Or run without installing (one-off):
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npx -y -p @depths/waves waves --help
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### 1) Get the agent prompt + schemas
|
|
85
|
-
|
|
86
|
-
Machine-readable payload (recommended for agents):
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
npx waves prompt --format json --out ./waves-prompt.json
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Human-readable system prompt:
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
npx waves prompt --format text --out ./waves-system-prompt.txt
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Schemas only:
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npx waves schema --kind all --pretty --out ./waves-schemas.json
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
If you register custom components at module import time, include them with repeatable `--register`:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
npx waves prompt --format json --register ./src/register-waves-components.ts
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
### 2) Write IR JSON
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
npx waves write-ir --template basic --pretty --out ./video.ir.json
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### 3) Validate IR JSON
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
npx waves validate --in ./video.ir.json
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
Structured validation result:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
npx waves validate --in ./video.ir.json --format json
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
### 4) Render MP4
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
npx waves render --in ./video.ir.json --out ./output.mp4 --codec h264 --crf 28 --concurrency 1
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
If your IR references `"/assets/..."` paths, pass `--publicDir` and ensure the files exist at `${publicDir}/assets/...`:
|
|
135
|
-
|
|
136
|
-
```bash
|
|
137
|
-
npx waves render --in ./video.ir.json --out ./output.mp4 --publicDir ./public
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Exit codes
|
|
141
|
-
|
|
142
|
-
- `0`: success
|
|
143
|
-
- `1`: usage error (invalid flags/command)
|
|
144
|
-
- `2`: validation failure (invalid JSON or IR validation errors)
|
|
145
|
-
- `3`: render failure
|
|
146
|
-
- `4`: I/O error (missing/unreadable/unwritable files)
|
|
147
|
-
- `5`: internal error (bug)
|
|
148
|
-
|
|
149
|
-
## Quickstart (one function)
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
import { renderVideo } from '@depths/waves';
|
|
151
|
+
Node: see `package.json` engines (this repo targets Node 22+).
|
|
153
152
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
type: 'Text',
|
|
168
|
-
timing: { from: 0, durationInFrames: 90 },
|
|
169
|
-
props: { content: 'Hello Waves', fontSize: 72, animation: 'fade' }
|
|
170
|
-
}
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
|
-
},
|
|
175
|
-
{ outputPath: './output.mp4' }
|
|
176
|
-
);
|
|
153
|
+
## CLI (agent workflow)
|
|
154
|
+
|
|
155
|
+
Waves ships a `waves` CLI so a locally running AI agent (or any terminal user) can:
|
|
156
|
+
|
|
157
|
+
1) fetch a system prompt + JSON Schemas + catalog
|
|
158
|
+
2) write a starter IR file
|
|
159
|
+
3) validate the IR
|
|
160
|
+
4) render an MP4 from the IR
|
|
161
|
+
|
|
162
|
+
Install locally (recommended):
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm i -D @depths/waves
|
|
177
166
|
```
|
|
178
167
|
|
|
179
|
-
|
|
168
|
+
Run via the local bin:
|
|
180
169
|
|
|
181
|
-
|
|
170
|
+
```bash
|
|
171
|
+
npx waves --help
|
|
172
|
+
```
|
|
182
173
|
|
|
183
|
-
|
|
174
|
+
### Typical agent loop
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
- `video`: dimensions, fps, total duration
|
|
187
|
-
- `scenes`: an array of `Scene` components
|
|
176
|
+
For an agent that can execute terminal commands, the recommended flow is:
|
|
188
177
|
|
|
189
|
-
|
|
178
|
+
1. `waves prompt --format json --out waves-prompt.json`
|
|
179
|
+
2. Use `waves-prompt.json.systemPrompt` + `waves-prompt.json.schemas` + `waves-prompt.json.catalog` as the authoring contract for the model
|
|
180
|
+
3. Have the model output a single JSON object (Video IR)
|
|
181
|
+
4. Write that JSON to `video.v2.json`
|
|
182
|
+
5. `waves validate --in video.v2.json` until it passes
|
|
183
|
+
6. `waves render --in video.v2.json --out output.mp4 ...`
|
|
190
184
|
|
|
191
|
-
###
|
|
185
|
+
### 1) Get the prompt payload (system prompt + schemas + catalog)
|
|
192
186
|
|
|
193
|
-
-
|
|
194
|
-
- Scenes are expected to be sequential:
|
|
195
|
-
- Scene 0 starts at frame 0
|
|
196
|
-
- Scene N starts exactly where Scene N-1 ends
|
|
197
|
-
- Sum of scene durations must equal `video.durationInFrames`
|
|
198
|
-
- Child components (inside a `Scene`) must fit within the scene duration.
|
|
187
|
+
Machine-readable payload (recommended for agents):
|
|
199
188
|
|
|
200
|
-
|
|
189
|
+
```bash
|
|
190
|
+
npx waves prompt --format json --pretty --out ./waves-prompt.json
|
|
191
|
+
```
|
|
201
192
|
|
|
202
|
-
|
|
193
|
+
Human-readable system prompt:
|
|
203
194
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- `metadata`: descriptions and examples useful for LLM prompting
|
|
195
|
+
```bash
|
|
196
|
+
npx waves prompt --format text --out ./waves-system-prompt.txt
|
|
197
|
+
```
|
|
208
198
|
|
|
209
|
-
|
|
199
|
+
Schemas only:
|
|
210
200
|
|
|
211
|
-
|
|
201
|
+
```bash
|
|
202
|
+
npx waves schema --kind all --pretty --out ./waves-schemas.json
|
|
203
|
+
```
|
|
212
204
|
|
|
213
|
-
|
|
214
|
-
2. Generate a temporary Remotion entry point
|
|
215
|
-
3. Bundle using `@remotion/bundler`
|
|
216
|
-
4. Select the composition using `@remotion/renderer`
|
|
217
|
-
5. Render the media to `outputPath`
|
|
205
|
+
Component catalog (grouped by category):
|
|
218
206
|
|
|
219
|
-
|
|
207
|
+
```bash
|
|
208
|
+
npx waves catalog
|
|
209
|
+
npx waves catalog --format json --pretty --out ./waves-catalog.json
|
|
210
|
+
```
|
|
220
211
|
|
|
221
|
-
|
|
212
|
+
If you register custom components at module import time, include them with repeatable `--register`:
|
|
222
213
|
|
|
223
|
-
|
|
214
|
+
```bash
|
|
215
|
+
npx waves prompt --format json --register ./src/register-waves-components.ts
|
|
216
|
+
npx waves schema --kind components --register ./src/register-waves-components.ts
|
|
217
|
+
npx waves validate --in ./video.v2.json --register ./src/register-waves-components.ts
|
|
218
|
+
```
|
|
224
219
|
|
|
225
|
-
|
|
220
|
+
Important notes on `--register`:
|
|
226
221
|
|
|
227
|
-
|
|
222
|
+
- The value must be a Node-loadable module at runtime (typically a `.js`/`.mjs` file).
|
|
223
|
+
- If you pass a path, prefer an absolute path or a relative path with `./`.
|
|
224
|
+
- A registration module usually calls `globalRegistry.register(...)` to add custom components before validation/rendering.
|
|
225
|
+
- Re-registering an existing `type` throws (registry types are unique).
|
|
228
226
|
|
|
229
|
-
|
|
230
|
-
import { VideoIRSchema } from '@depths/waves';
|
|
227
|
+
### 2) Write IR JSON
|
|
231
228
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
// parsed.error.issues
|
|
235
|
-
}
|
|
229
|
+
```bash
|
|
230
|
+
npx waves write-ir --template basic --pretty --out ./video.v2.json
|
|
236
231
|
```
|
|
237
232
|
|
|
238
|
-
###
|
|
233
|
+
### 3) Validate IR JSON
|
|
239
234
|
|
|
240
|
-
|
|
235
|
+
```bash
|
|
236
|
+
npx waves validate --in ./video.v2.json
|
|
237
|
+
```
|
|
241
238
|
|
|
242
|
-
|
|
243
|
-
import { IRValidator } from '@depths/waves';
|
|
239
|
+
Structured validation result:
|
|
244
240
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (!result.success) {
|
|
248
|
-
// result.errors: { path: string[]; message: string; code: string }[]
|
|
249
|
-
}
|
|
241
|
+
```bash
|
|
242
|
+
npx waves validate --in ./video.v2.json --format json
|
|
250
243
|
```
|
|
251
244
|
|
|
252
|
-
|
|
245
|
+
### 4) Render MP4
|
|
253
246
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
247
|
+
```bash
|
|
248
|
+
npx waves render --in ./video.v2.json --out ./output.mp4 --codec h264 --crf 28 --concurrency 1
|
|
249
|
+
```
|
|
257
250
|
|
|
258
|
-
|
|
251
|
+
If your IR references `"/assets/..."` paths, pass `--publicDir` and ensure the files exist at `${publicDir}/assets/...`:
|
|
259
252
|
|
|
260
|
-
```
|
|
261
|
-
|
|
253
|
+
```bash
|
|
254
|
+
npx waves render --in ./video.v2.json --out ./output.mp4 --publicDir ./public
|
|
262
255
|
```
|
|
263
256
|
|
|
264
|
-
|
|
257
|
+
### Exit codes
|
|
265
258
|
|
|
266
|
-
|
|
259
|
+
- `0`: success
|
|
260
|
+
- `1`: usage error (invalid flags/command)
|
|
261
|
+
- `2`: validation failure (invalid JSON or IR validation errors)
|
|
262
|
+
- `3`: render failure
|
|
263
|
+
- `4`: I/O error (missing/unreadable/unwritable files)
|
|
264
|
+
- `5`: internal error (bug)
|
|
267
265
|
|
|
268
|
-
|
|
266
|
+
### Command reference
|
|
269
267
|
|
|
270
|
-
|
|
271
|
-
|
|
268
|
+
This section documents every command and flag supported by the current CLI implementation (`src/cli.ts`).
|
|
269
|
+
|
|
270
|
+
#### `waves --help` / `waves help`
|
|
271
|
+
|
|
272
|
+
Prints a short help text.
|
|
273
|
+
|
|
274
|
+
#### `waves --version`
|
|
275
|
+
|
|
276
|
+
Prints the package version (e.g. `0.2.0`).
|
|
277
|
+
|
|
278
|
+
#### `waves prompt`
|
|
279
|
+
|
|
280
|
+
Outputs an "agent-ready" payload that includes:
|
|
281
|
+
|
|
282
|
+
- `systemPrompt`: a full system prompt string
|
|
283
|
+
- `schemas.videoIR`: JSON Schema for authoring IR (segments mode)
|
|
284
|
+
- `schemas.components`: a JSON object keyed by component `type` containing props JSON Schemas + metadata
|
|
285
|
+
- `catalog`: categories + items (flattened list for easier prompting / UI)
|
|
286
|
+
|
|
287
|
+
Flags:
|
|
272
288
|
|
|
273
|
-
|
|
289
|
+
- `--format text|json` (default `text`)
|
|
290
|
+
- `--maxChars <n>` (text format only; truncates the prompt)
|
|
291
|
+
- `--pretty` (json format only)
|
|
292
|
+
- `--out <path>` (optional; writes the output to a file in addition to stdout)
|
|
293
|
+
- `--register <module>` (repeatable)
|
|
294
|
+
|
|
295
|
+
Examples:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npx waves prompt --format json --pretty --out ./waves-prompt.json
|
|
299
|
+
npx waves prompt --format text --maxChars 4000 --out ./waves-system-prompt.txt
|
|
274
300
|
```
|
|
275
301
|
|
|
276
|
-
|
|
302
|
+
#### `waves schema`
|
|
277
303
|
|
|
278
|
-
|
|
304
|
+
Outputs JSON Schemas only.
|
|
279
305
|
|
|
280
|
-
|
|
306
|
+
Flags:
|
|
281
307
|
|
|
282
|
-
-
|
|
283
|
-
-
|
|
284
|
-
-
|
|
308
|
+
- `--kind video-ir|components|all` (default `all`)
|
|
309
|
+
- `--pretty`
|
|
310
|
+
- `--out <path>` (optional; writes output to a file in addition to stdout)
|
|
311
|
+
- `--register <module>` (repeatable; affects `--kind components` and `all`)
|
|
285
312
|
|
|
286
|
-
|
|
287
|
-
import { renderVideo } from '@depths/waves';
|
|
313
|
+
Examples:
|
|
288
314
|
|
|
289
|
-
|
|
315
|
+
```bash
|
|
316
|
+
npx waves schema --kind video-ir --pretty --out ./schema.video-ir.json
|
|
317
|
+
npx waves schema --kind components --pretty --out ./schema.components.json
|
|
290
318
|
```
|
|
291
319
|
|
|
292
|
-
|
|
320
|
+
#### `waves catalog`
|
|
293
321
|
|
|
294
|
-
|
|
322
|
+
Prints the built-in component catalog grouped by category.
|
|
295
323
|
|
|
296
|
-
|
|
297
|
-
|
|
324
|
+
Flags:
|
|
325
|
+
|
|
326
|
+
- `--format text|json` (default `text`)
|
|
327
|
+
- `--pretty` (json format only)
|
|
328
|
+
- `--out <path>` (optional; writes output to a file in addition to stdout)
|
|
329
|
+
- `--includeInternal` (includes internal-only types such as `Segment`)
|
|
330
|
+
- `--register <module>` (repeatable; adds additional registered types)
|
|
331
|
+
|
|
332
|
+
Examples:
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npx waves catalog
|
|
336
|
+
npx waves catalog --format json --pretty --out ./waves-catalog.json
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
#### `waves write-ir`
|
|
340
|
+
|
|
341
|
+
Writes a starter IR JSON file (always `version: "2.0"` segments mode).
|
|
298
342
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
343
|
+
Flags:
|
|
344
|
+
|
|
345
|
+
- `--template minimal|basic` (default `minimal`)
|
|
346
|
+
- `--pretty`
|
|
347
|
+
- `--out <path>` (required)
|
|
348
|
+
|
|
349
|
+
Examples:
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
npx waves write-ir --template minimal --pretty --out ./video.v2.json
|
|
353
|
+
npx waves write-ir --template basic --pretty --out ./examples/basic.v2.json
|
|
307
354
|
```
|
|
308
355
|
|
|
309
|
-
|
|
356
|
+
#### `waves validate`
|
|
310
357
|
|
|
311
|
-
|
|
312
|
-
- `codec`: one of `'h264' | 'h265' | 'vp8' | 'vp9'` (default: `h264`)
|
|
313
|
-
- `crf`: quality parameter (lower is higher quality)
|
|
314
|
-
- `concurrency`: number or percentage string supported by Remotion renderer
|
|
315
|
-
- `publicDir`: optional directory for Remotion `staticFile()` assets
|
|
358
|
+
Validates an IR JSON file.
|
|
316
359
|
|
|
317
|
-
|
|
360
|
+
Flags:
|
|
318
361
|
|
|
319
|
-
-
|
|
320
|
-
- `
|
|
362
|
+
- `--in <path>` (required)
|
|
363
|
+
- `--format text|json` (default `text`)
|
|
364
|
+
- `--pretty` (json format only)
|
|
365
|
+
- `--register <module>` (repeatable)
|
|
321
366
|
|
|
322
|
-
|
|
367
|
+
Behavior:
|
|
323
368
|
|
|
324
|
-
|
|
369
|
+
- text format prints `ok` to stdout on success, otherwise prints a human-readable list to stderr and exits non-zero.
|
|
370
|
+
- json format prints `{ "success": true }` or `{ "success": false, "errors": [...] }` to stdout.
|
|
371
|
+
|
|
372
|
+
Examples:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
npx waves validate --in ./video.v2.json
|
|
376
|
+
npx waves validate --in ./video.v2.json --format json --pretty
|
|
377
|
+
```
|
|
325
378
|
|
|
326
|
-
|
|
327
|
-
2. Absolute “public” paths: `/assets/foo.png`
|
|
379
|
+
#### `waves render`
|
|
328
380
|
|
|
329
|
-
|
|
381
|
+
Renders an MP4 from an IR JSON file.
|
|
330
382
|
|
|
331
|
-
|
|
332
|
-
- Example: `src: "/assets/a.png"` becomes `staticFile("assets/a.png")`.
|
|
383
|
+
Flags:
|
|
333
384
|
|
|
334
|
-
|
|
385
|
+
- `--in <path>` (required)
|
|
386
|
+
- `--out <path>` (required)
|
|
387
|
+
- `--publicDir <path>` (optional; required if your IR uses `/assets/...` paths)
|
|
388
|
+
- `--codec h264|h265|vp8|vp9` (optional; default `h264`)
|
|
389
|
+
- `--crf <n>` (optional; forwarded to Remotion renderer)
|
|
390
|
+
- `--concurrency <n|string>` (optional; forwarded to Remotion renderer)
|
|
391
|
+
- `--register <module>` (repeatable)
|
|
392
|
+
- `--pretty` (only affects the formatting of error JSON when render fails)
|
|
335
393
|
|
|
394
|
+
Examples:
|
|
395
|
+
|
|
396
|
+
```bash
|
|
397
|
+
npx waves render --in ./video.v2.json --out ./output.mp4 --codec h264 --crf 28 --concurrency 1
|
|
398
|
+
npx waves render --in ./video.v2.json --out ./output.mp4 --publicDir ./public
|
|
336
399
|
```
|
|
337
|
-
|
|
400
|
+
|
|
401
|
+
## IR v2.0 (authoring contract)
|
|
402
|
+
|
|
403
|
+
v0.2.0 targets `version: "2.0"` only.
|
|
404
|
+
|
|
405
|
+
### Recommended: `segments[]` (high-level)
|
|
406
|
+
|
|
407
|
+
In segments mode, you provide sequential segments and Waves compiles them into an explicit timed timeline. You usually do not need to specify `timing` on nodes.
|
|
408
|
+
|
|
409
|
+
Key rules:
|
|
410
|
+
|
|
411
|
+
- Segment overlap is controlled by `transitionToNext.durationInFrames`.
|
|
412
|
+
- `transitionToNext` is only valid when there is a "next" segment (i.e. it is not allowed on the last segment).
|
|
413
|
+
- The total video duration must match the compiled timeline end:
|
|
414
|
+
- `video.durationInFrames = sum(segment.durationInFrames) - sum(overlap)`
|
|
415
|
+
- where `overlap = transitionToNext.durationInFrames` for each segment that has a next segment.
|
|
416
|
+
|
|
417
|
+
Minimal example:
|
|
418
|
+
|
|
419
|
+
```json
|
|
420
|
+
{
|
|
421
|
+
"version": "2.0",
|
|
422
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 60 },
|
|
423
|
+
"segments": [
|
|
424
|
+
{
|
|
425
|
+
"id": "scene-1",
|
|
426
|
+
"durationInFrames": 60,
|
|
427
|
+
"root": {
|
|
428
|
+
"id": "root",
|
|
429
|
+
"type": "Scene",
|
|
430
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
431
|
+
"children": [{ "id": "t1", "type": "Text", "props": { "content": "Hello" } }]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
338
436
|
```
|
|
339
437
|
|
|
340
|
-
|
|
438
|
+
Supported `transitionToNext.type` values (segment overlap transitions):
|
|
439
|
+
|
|
440
|
+
- `FadeTransition`
|
|
441
|
+
- `SlideTransition` (`props`: `{ direction: "left"|"right"|"up"|"down", distance?: number }`)
|
|
442
|
+
- `ZoomTransition` (`props`: `{ type: "zoomIn"|"zoomOut" }`)
|
|
443
|
+
- `WipeTransition` (`props`: `{ direction: "left"|"right"|"up"|"down"|"diagonal", softEdge?: boolean }`)
|
|
444
|
+
- `CircularReveal` (`props`: `{ direction: "open"|"close", center?: { x: 0..1, y: 0..1 } }`)
|
|
445
|
+
|
|
446
|
+
### Escape hatch: `timeline[]` (low-level)
|
|
341
447
|
|
|
342
|
-
|
|
448
|
+
In timeline mode you provide explicit timings. Each node's `timing` is relative to its parent sequence (nested timing is "local").
|
|
343
449
|
|
|
344
|
-
|
|
450
|
+
Notes:
|
|
451
|
+
|
|
452
|
+
- Root `timing` is optional; if omitted, Waves treats the node as spanning the full video duration.
|
|
453
|
+
- Child `timing` is optional; if omitted, Waves treats the child as spanning the full parent duration.
|
|
454
|
+
|
|
455
|
+
```json
|
|
456
|
+
{
|
|
457
|
+
"version": "2.0",
|
|
458
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 60 },
|
|
459
|
+
"timeline": [
|
|
460
|
+
{
|
|
461
|
+
"id": "scene",
|
|
462
|
+
"type": "Scene",
|
|
463
|
+
"timing": { "from": 0, "durationInFrames": 60 },
|
|
464
|
+
"props": { "background": { "type": "color", "value": "#000000" } }
|
|
465
|
+
}
|
|
466
|
+
]
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
### Component nodes
|
|
471
|
+
|
|
472
|
+
Nodes are structural (the IR does not hard-code component types):
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
type ComponentNode = {
|
|
476
|
+
id: string;
|
|
477
|
+
type: string; // must be registered at validate/render time
|
|
478
|
+
props?: Record<string, unknown>;
|
|
479
|
+
timing?: { from: number; durationInFrames: number };
|
|
480
|
+
children?: ComponentNode[];
|
|
481
|
+
};
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Validation uses the registry to enforce:
|
|
485
|
+
|
|
486
|
+
- unknown component types (error)
|
|
487
|
+
- props schemas (Zod validation, defaults applied)
|
|
488
|
+
- whether a component can have `children` (metadata contract)
|
|
489
|
+
|
|
490
|
+
## Components (primitives + composites)
|
|
491
|
+
|
|
492
|
+
Waves renders only components that have been explicitly registered in the `ComponentRegistry`. Each component is defined by:
|
|
493
|
+
|
|
494
|
+
- a **string type** (e.g. `"Scene"`, `"TypewriterText"`)
|
|
495
|
+
- a **React component** implementation (Remotion primitives + CSS)
|
|
496
|
+
- a **Zod props schema** (validation + defaults)
|
|
497
|
+
- **metadata** (kind/category/description/LLM guidance/children contract)
|
|
498
|
+
|
|
499
|
+
### Primitives vs composites
|
|
500
|
+
|
|
501
|
+
- **Primitives** are low-level building blocks (layout, text, media). They are intentionally generic.
|
|
502
|
+
- **Composites** are higher-level building blocks (shadcn-like) that encode common video patterns: titles, lower thirds, social cards, charts, transitions, etc.
|
|
503
|
+
|
|
504
|
+
In general:
|
|
505
|
+
|
|
506
|
+
- Prefer composites for agent-authored videos (less "design work" for the model).
|
|
507
|
+
- Use primitives when you need precise control or to build new composites.
|
|
508
|
+
|
|
509
|
+
### Categories and children contracts
|
|
510
|
+
|
|
511
|
+
Each component is categorized (`text`, `layout`, `media`, `transition`, etc.) to help an agent choose the right tool.
|
|
512
|
+
|
|
513
|
+
Some components can have nested `children` in the IR. This is controlled by metadata:
|
|
514
|
+
|
|
515
|
+
- `acceptsChildren: true|false`
|
|
516
|
+
- optional `minChildren` / `maxChildren` constraints
|
|
517
|
+
|
|
518
|
+
If a component does not accept children and the IR provides `children`, validation fails before rendering.
|
|
519
|
+
|
|
520
|
+
### Internal props: `__wavesDurationInFrames`
|
|
521
|
+
|
|
522
|
+
At render time, Waves injects `__wavesDurationInFrames` into every component. This is the duration of the node's `Sequence`.
|
|
523
|
+
|
|
524
|
+
This allows components to implement "in/out" animations without the author hand-computing timings.
|
|
525
|
+
|
|
526
|
+
This prop is *not* part of the author-facing props schema and does not appear in the tables below.
|
|
527
|
+
|
|
528
|
+
### Keeping docs in sync
|
|
529
|
+
|
|
530
|
+
The tables below are generated from the live registry JSON Schemas to reduce drift.
|
|
531
|
+
|
|
532
|
+
Regenerate after changing component props/metadata:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
npm run build
|
|
536
|
+
node scripts/generate-readme-components.mjs
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
<!-- BEGIN GENERATED: COMPONENTS -->
|
|
540
|
+
|
|
541
|
+
<!-- generated by scripts/generate-readme-components.mjs; do not edit by hand -->
|
|
542
|
+
|
|
543
|
+
### Components summary
|
|
544
|
+
|
|
545
|
+
| Type | Kind | Category | Children | Internal | Description |
|
|
546
|
+
| - | - | - | - | - | - |
|
|
547
|
+
| `IntroScene` | composite | branding | no | no | Branded intro scene (logo + company name + optional tagline) |
|
|
548
|
+
| `LogoReveal` | composite | branding | no | no | Logo intro animation (fade/scale/rotate/slide), optionally with a sound effect |
|
|
549
|
+
| `OutroScene` | composite | branding | no | no | End screen with logo, message, optional CTA buttons and social handles |
|
|
550
|
+
| `Watermark` | composite | branding | no | no | Persistent logo/text watermark in a corner |
|
|
551
|
+
| `AnimatedCounter` | composite | data | no | no | Animated numeric counter (spring or linear), optionally with an icon and suffix |
|
|
552
|
+
| `BarChart` | composite | data | no | no | Animated bar chart (vertical or horizontal) |
|
|
553
|
+
| `LineGraph` | composite | data | no | no | Animated line graph (SVG) with draw/reveal modes |
|
|
554
|
+
| `ProgressBar` | composite | data | no | no | Animated progress bar that fills over the component duration |
|
|
555
|
+
| `ProgressRing` | composite | data | no | no | Circular progress indicator (SVG) that animates from 0 to percentage over duration |
|
|
556
|
+
| `Image` | primitive | image | no | no | Full-frame image with object-fit options |
|
|
557
|
+
| `ImageCollage` | composite | image | no | no | Collage of multiple images in a grid/stack/scatter layout with staggered entrances |
|
|
558
|
+
| `ImageReveal` | composite | image | no | no | Reveals an image with wipe/expand/iris entrance effects |
|
|
559
|
+
| `ImageSequence` | composite | image | no | no | Plays a numbered image sequence (frame-by-frame) |
|
|
560
|
+
| `ImageWithCaption` | composite | image | no | no | Image with a caption strip (top/bottom) or overlay caption |
|
|
561
|
+
| `KenBurnsImage` | composite | image | no | no | Slow zoom and pan (Ken Burns effect) for a still image |
|
|
562
|
+
| `Box` | primitive | layout | yes | no | Positioned container box for layout and backgrounds |
|
|
563
|
+
| `CardStack` | composite | layout | no | no | Sequential stacked cards (2-5) with flip/slide/fade transitions |
|
|
564
|
+
| `Grid` | primitive | layout | yes | no | Grid layout container with configurable rows/columns |
|
|
565
|
+
| `GridLayout` | composite | layout | yes (1..∞) | no | Simple responsive grid layout for child components |
|
|
566
|
+
| `Scene` | primitive | layout | yes | no | Scene container with a background and nested children |
|
|
567
|
+
| `Segment` | primitive | layout | yes (1..1) | yes | Internal segment wrapper (used by v2 segments compiler) |
|
|
568
|
+
| `Shape` | primitive | layout | no | no | Simple rect/circle shape for UI accents |
|
|
569
|
+
| `SplitScreen` | composite | layout | yes (2..2) | no | Two-panel split screen layout |
|
|
570
|
+
| `Stack` | primitive | layout | yes | no | Flexbox stack layout (row/column) with gap and alignment |
|
|
571
|
+
| `ThirdLowerBanner` | composite | layout | no | no | Broadcast-style lower-third banner with name/title and optional avatar |
|
|
572
|
+
| `Audio` | primitive | media | no | no | Plays an audio file with optional trimming and fade in/out |
|
|
573
|
+
| `Video` | primitive | media | no | no | Full-frame video with object-fit options |
|
|
574
|
+
| `VideoWithOverlay` | composite | media | no | no | Video background with an optional overlay (text/logo/gradient) |
|
|
575
|
+
| `InstagramStory` | composite | social | no | no | Instagram story-style layout with profile header, text overlay, and optional sticker |
|
|
576
|
+
| `TikTokCaption` | composite | social | no | no | TikTok-style captions with stroke and optional word highlighting |
|
|
577
|
+
| `TwitterCard` | composite | social | no | no | Twitter/X post card layout with author header and optional image |
|
|
578
|
+
| `YouTubeThumbnail` | composite | social | no | no | YouTube-style thumbnail layout (16:9) with bold title and optional face cutout |
|
|
579
|
+
| `CountUpText` | composite | text | no | no | Counts from a start value to an end value with formatting options |
|
|
580
|
+
| `GlitchText` | composite | text | no | no | Cyberpunk-style glitch text with RGB split jitter |
|
|
581
|
+
| `KineticTypography` | composite | text | no | no | Rhythmic single-word kinetic typography driven by a timing array |
|
|
582
|
+
| `OutlineText` | composite | text | no | no | Outlined title text with simple draw/fill animation |
|
|
583
|
+
| `SplitText` | composite | text | no | no | Animated text where each word or letter enters with a staggered effect |
|
|
584
|
+
| `SubtitleText` | composite | text | no | no | Caption/subtitle box with fade in/out and optional highlighted words |
|
|
585
|
+
| `Text` | primitive | text | no | no | Displays animated text with positioning and animation options |
|
|
586
|
+
| `TypewriterText` | composite | text | no | no | Character-by-character text reveal with optional blinking cursor |
|
|
587
|
+
| `CircularReveal` | composite | transition | yes (1..∞) | no | Circular iris reveal/hide transition wrapper |
|
|
588
|
+
| `FadeTransition` | composite | transition | yes (1..∞) | no | Fade in/out wrapper (used for segment transitions and overlays) |
|
|
589
|
+
| `SlideTransition` | composite | transition | yes (1..∞) | no | Slide in/out wrapper (used for segment transitions and overlays) |
|
|
590
|
+
| `WipeTransition` | composite | transition | yes (1..∞) | no | Directional wipe reveal/hide wrapper transition |
|
|
591
|
+
| `ZoomTransition` | composite | transition | yes (1..∞) | no | Zoom in/out wrapper transition |
|
|
592
|
+
|
|
593
|
+
### Components reference
|
|
594
|
+
|
|
595
|
+
#### Category: `branding`
|
|
596
|
+
|
|
597
|
+
##### `IntroScene`
|
|
598
|
+
|
|
599
|
+
- kind: `composite`
|
|
600
|
+
- category: `branding`
|
|
601
|
+
- internal: `false`
|
|
602
|
+
- children: `no`
|
|
603
|
+
- description: Branded intro scene (logo + company name + optional tagline)
|
|
604
|
+
- llmGuidance: Use as the first segment. Works best at 3-5 seconds. musicTrack can add ambience.
|
|
605
|
+
|
|
606
|
+
Props:
|
|
607
|
+
|
|
608
|
+
| Prop | Type | Required | Default | Notes |
|
|
609
|
+
| - | - | - | - | - |
|
|
610
|
+
| `backgroundColor` | string | yes | "#000000" | |
|
|
611
|
+
| `companyName` | string | yes | | minLength=1 |
|
|
612
|
+
| `logoSrc` | string | yes | | minLength=1 |
|
|
613
|
+
| `musicTrack` | string | no | | |
|
|
614
|
+
| `primaryColor` | string | yes | "#FFFFFF" | |
|
|
615
|
+
| `tagline` | string | no | | |
|
|
616
|
+
|
|
617
|
+
##### `LogoReveal`
|
|
618
|
+
|
|
619
|
+
- kind: `composite`
|
|
620
|
+
- category: `branding`
|
|
621
|
+
- internal: `false`
|
|
622
|
+
- children: `no`
|
|
623
|
+
- description: Logo intro animation (fade/scale/rotate/slide), optionally with a sound effect
|
|
624
|
+
- llmGuidance: Use for intros/outros. Keep the logo high-contrast and centered. soundEffect can be a short sting.
|
|
345
625
|
|
|
346
626
|
Props:
|
|
347
627
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
628
|
+
| Prop | Type | Required | Default | Notes |
|
|
629
|
+
| - | - | - | - | - |
|
|
630
|
+
| `backgroundColor` | string | yes | "#000000" | |
|
|
631
|
+
| `effect` | enum("fade" \| "scale" \| "rotate" \| "slide") | yes | "scale" | |
|
|
632
|
+
| `logoSrc` | string | yes | | minLength=1 |
|
|
633
|
+
| `soundEffect` | string | no | | |
|
|
634
|
+
|
|
635
|
+
##### `OutroScene`
|
|
352
636
|
|
|
353
|
-
|
|
637
|
+
- kind: `composite`
|
|
638
|
+
- category: `branding`
|
|
639
|
+
- internal: `false`
|
|
640
|
+
- children: `no`
|
|
641
|
+
- description: End screen with logo, message, optional CTA buttons and social handles
|
|
642
|
+
- llmGuidance: Use as the last segment. Keep CTAs <=3 for clarity.
|
|
354
643
|
|
|
355
|
-
|
|
644
|
+
Props:
|
|
645
|
+
|
|
646
|
+
| Prop | Type | Required | Default | Notes |
|
|
647
|
+
| - | - | - | - | - |
|
|
648
|
+
| `backgroundColor` | string | yes | "#000000" | |
|
|
649
|
+
| `ctaButtons` | array<object> | no | | maxItems=3 |
|
|
650
|
+
| `logoSrc` | string | yes | | minLength=1 |
|
|
651
|
+
| `message` | string | yes | "Thank You" | |
|
|
652
|
+
| `socialHandles` | array<object> | no | | maxItems=4 |
|
|
356
653
|
|
|
357
|
-
|
|
654
|
+
##### `Watermark`
|
|
358
655
|
|
|
359
|
-
|
|
656
|
+
- kind: `composite`
|
|
657
|
+
- category: `branding`
|
|
658
|
+
- internal: `false`
|
|
659
|
+
- children: `no`
|
|
660
|
+
- description: Persistent logo/text watermark in a corner
|
|
661
|
+
- llmGuidance: Use subtle opacity (0.3-0.6). bottomRight is standard.
|
|
360
662
|
|
|
361
663
|
Props:
|
|
362
664
|
|
|
363
|
-
|
|
364
|
-
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
665
|
+
| Prop | Type | Required | Default | Notes |
|
|
666
|
+
| - | - | - | - | - |
|
|
667
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
668
|
+
| `opacity` | number | yes | 0.5 | min=0.1, max=1 |
|
|
669
|
+
| `position` | enum("topLeft" \| "topRight" \| "bottomLeft" \| "bottomRight") | yes | "bottomRight" | |
|
|
670
|
+
| `size` | number | yes | 60 | min=30, max=150 |
|
|
671
|
+
| `src` | string | no | | |
|
|
672
|
+
| `text` | string | no | | |
|
|
673
|
+
| `type` | enum("logo" \| "text") | yes | "logo" | |
|
|
368
674
|
|
|
369
|
-
|
|
675
|
+
#### Category: `data`
|
|
370
676
|
|
|
371
|
-
|
|
677
|
+
##### `AnimatedCounter`
|
|
678
|
+
|
|
679
|
+
- kind: `composite`
|
|
680
|
+
- category: `data`
|
|
681
|
+
- internal: `false`
|
|
682
|
+
- children: `no`
|
|
683
|
+
- description: Animated numeric counter (spring or linear), optionally with an icon and suffix
|
|
684
|
+
- llmGuidance: Use for big stats. animationType="spring" feels natural. suffix for units (%, K, M).
|
|
372
685
|
|
|
373
686
|
Props:
|
|
374
687
|
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
688
|
+
| Prop | Type | Required | Default | Notes |
|
|
689
|
+
| - | - | - | - | - |
|
|
690
|
+
| `animationType` | enum("spring" \| "linear") | yes | "spring" | |
|
|
691
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
692
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
693
|
+
| `fontSize` | number | yes | 96 | min=8, max=300 |
|
|
694
|
+
| `fontWeight` | integer | yes | 700 | min=100, max=900 |
|
|
695
|
+
| `from` | number | yes | 0 | |
|
|
696
|
+
| `icon` | string | no | | |
|
|
697
|
+
| `suffix` | string | no | | |
|
|
698
|
+
| `to` | number | yes | 100 | |
|
|
699
|
+
|
|
700
|
+
##### `BarChart`
|
|
701
|
+
|
|
702
|
+
- kind: `composite`
|
|
703
|
+
- category: `data`
|
|
704
|
+
- internal: `false`
|
|
705
|
+
- children: `no`
|
|
706
|
+
- description: Animated bar chart (vertical or horizontal)
|
|
707
|
+
- llmGuidance: Use 2-6 bars. Provide maxValue to lock scale across multiple charts.
|
|
380
708
|
|
|
381
|
-
|
|
709
|
+
Props:
|
|
710
|
+
|
|
711
|
+
| Prop | Type | Required | Default | Notes |
|
|
712
|
+
| - | - | - | - | - |
|
|
713
|
+
| `data` | array<object> | yes | | minItems=2, maxItems=8 |
|
|
714
|
+
| `maxValue` | number | no | | |
|
|
715
|
+
| `orientation` | enum("horizontal" \| "vertical") | yes | "vertical" | |
|
|
716
|
+
| `showGrid` | boolean | yes | false | |
|
|
717
|
+
| `showValues` | boolean | yes | true | |
|
|
718
|
+
|
|
719
|
+
##### `LineGraph`
|
|
720
|
+
|
|
721
|
+
- kind: `composite`
|
|
722
|
+
- category: `data`
|
|
723
|
+
- internal: `false`
|
|
724
|
+
- children: `no`
|
|
725
|
+
- description: Animated line graph (SVG) with draw/reveal modes
|
|
726
|
+
- llmGuidance: Use 5-20 points. animate="draw" traces the line; animate="reveal" wipes it left-to-right.
|
|
727
|
+
|
|
728
|
+
Props:
|
|
729
|
+
|
|
730
|
+
| Prop | Type | Required | Default | Notes |
|
|
731
|
+
| - | - | - | - | - |
|
|
732
|
+
| `animate` | enum("draw" \| "reveal") | yes | "draw" | |
|
|
733
|
+
| `color` | string | yes | "#00FF00" | |
|
|
734
|
+
| `data` | array<object> | yes | | minItems=2, maxItems=50 |
|
|
735
|
+
| `fillArea` | boolean | yes | false | |
|
|
736
|
+
| `showDots` | boolean | yes | true | |
|
|
737
|
+
| `strokeWidth` | number | yes | 3 | min=1, max=10 |
|
|
738
|
+
|
|
739
|
+
##### `ProgressBar`
|
|
740
|
+
|
|
741
|
+
- kind: `composite`
|
|
742
|
+
- category: `data`
|
|
743
|
+
- internal: `false`
|
|
744
|
+
- children: `no`
|
|
745
|
+
- description: Animated progress bar that fills over the component duration
|
|
746
|
+
- llmGuidance: Use for loading/countdowns. showPercentage=true is helpful for clarity.
|
|
747
|
+
|
|
748
|
+
Props:
|
|
749
|
+
|
|
750
|
+
| Prop | Type | Required | Default | Notes |
|
|
751
|
+
| - | - | - | - | - |
|
|
752
|
+
| `backgroundColor` | string | yes | "rgba(255,255,255,0.2)" | |
|
|
753
|
+
| `color` | string | yes | "#00FF00" | |
|
|
754
|
+
| `height` | number | yes | 10 | min=5, max=50 |
|
|
755
|
+
| `label` | string | no | | |
|
|
756
|
+
| `position` | enum("top" \| "bottom") | yes | "bottom" | |
|
|
757
|
+
| `showPercentage` | boolean | yes | true | |
|
|
758
|
+
|
|
759
|
+
##### `ProgressRing`
|
|
760
|
+
|
|
761
|
+
- kind: `composite`
|
|
762
|
+
- category: `data`
|
|
763
|
+
- internal: `false`
|
|
764
|
+
- children: `no`
|
|
765
|
+
- description: Circular progress indicator (SVG) that animates from 0 to percentage over duration
|
|
766
|
+
- llmGuidance: Use for completion and goals. size 160-260 is typical. showLabel displays the percentage.
|
|
767
|
+
|
|
768
|
+
Props:
|
|
769
|
+
|
|
770
|
+
| Prop | Type | Required | Default | Notes |
|
|
771
|
+
| - | - | - | - | - |
|
|
772
|
+
| `backgroundColor` | string | yes | "rgba(255,255,255,0.2)" | |
|
|
773
|
+
| `color` | string | yes | "#00FF00" | |
|
|
774
|
+
| `percentage` | number | yes | | min=0, max=100 |
|
|
775
|
+
| `showLabel` | boolean | yes | true | |
|
|
776
|
+
| `size` | number | yes | 200 | min=100, max=500 |
|
|
777
|
+
| `strokeWidth` | number | yes | 20 | min=5, max=50 |
|
|
778
|
+
|
|
779
|
+
#### Category: `image`
|
|
780
|
+
|
|
781
|
+
##### `Image`
|
|
782
|
+
|
|
783
|
+
- kind: `primitive`
|
|
784
|
+
- category: `image`
|
|
785
|
+
- internal: `false`
|
|
786
|
+
- children: `no`
|
|
787
|
+
- description: Full-frame image with object-fit options
|
|
788
|
+
- llmGuidance: Use Image for pictures and backgrounds. Use fit="cover" for full-bleed, fit="contain" to avoid cropping.
|
|
789
|
+
|
|
790
|
+
Props:
|
|
791
|
+
|
|
792
|
+
| Prop | Type | Required | Default | Notes |
|
|
793
|
+
| - | - | - | - | - |
|
|
794
|
+
| `borderRadius` | number | yes | 0 | min=0 |
|
|
795
|
+
| `fit` | enum("cover" \| "contain") | yes | "cover" | |
|
|
796
|
+
| `opacity` | number | yes | 1 | min=0, max=1 |
|
|
797
|
+
| `src` | string | yes | | minLength=1 |
|
|
798
|
+
|
|
799
|
+
##### `ImageCollage`
|
|
800
|
+
|
|
801
|
+
- kind: `composite`
|
|
802
|
+
- category: `image`
|
|
803
|
+
- internal: `false`
|
|
804
|
+
- children: `no`
|
|
805
|
+
- description: Collage of multiple images in a grid/stack/scatter layout with staggered entrances
|
|
806
|
+
- llmGuidance: Use 2-6 images for best results. layout="grid" is clean; "scatter" is energetic.
|
|
807
|
+
|
|
808
|
+
Props:
|
|
809
|
+
|
|
810
|
+
| Prop | Type | Required | Default | Notes |
|
|
811
|
+
| - | - | - | - | - |
|
|
812
|
+
| `images` | array<object> | yes | | minItems=2, maxItems=9 |
|
|
813
|
+
| `layout` | enum("grid" \| "stack" \| "scatter") | yes | "grid" | |
|
|
814
|
+
| `stagger` | integer | yes | 5 | min=2, max=10 |
|
|
815
|
+
|
|
816
|
+
##### `ImageReveal`
|
|
817
|
+
|
|
818
|
+
- kind: `composite`
|
|
819
|
+
- category: `image`
|
|
820
|
+
- internal: `false`
|
|
821
|
+
- children: `no`
|
|
822
|
+
- description: Reveals an image with wipe/expand/iris entrance effects
|
|
823
|
+
- llmGuidance: Use wipe for directional reveals, expand for subtle pop-in, iris for circular mask openings.
|
|
824
|
+
|
|
825
|
+
Props:
|
|
826
|
+
|
|
827
|
+
| Prop | Type | Required | Default | Notes |
|
|
828
|
+
| - | - | - | - | - |
|
|
829
|
+
| `direction` | enum("left" \| "right" \| "top" \| "bottom" \| "center") | yes | "left" | |
|
|
830
|
+
| `revealType` | enum("wipe" \| "expand" \| "iris") | yes | "wipe" | |
|
|
831
|
+
| `src` | string | yes | | minLength=1 |
|
|
832
|
+
|
|
833
|
+
##### `ImageSequence`
|
|
834
|
+
|
|
835
|
+
- kind: `composite`
|
|
836
|
+
- category: `image`
|
|
837
|
+
- internal: `false`
|
|
838
|
+
- children: `no`
|
|
839
|
+
- description: Plays a numbered image sequence (frame-by-frame)
|
|
840
|
+
- llmGuidance: Use for exported sprite sequences. basePath can be /assets/seq and filePattern like img_{frame}.png.
|
|
841
|
+
|
|
842
|
+
Props:
|
|
843
|
+
|
|
844
|
+
| Prop | Type | Required | Default | Notes |
|
|
845
|
+
| - | - | - | - | - |
|
|
846
|
+
| `basePath` | string | yes | | minLength=1 |
|
|
847
|
+
| `filePattern` | string | yes | "frame_{frame}.png" | |
|
|
848
|
+
| `fps` | integer | yes | 30 | min=1, max=120 |
|
|
849
|
+
| `frameCount` | integer | yes | | max=9007199254740991 |
|
|
850
|
+
|
|
851
|
+
##### `ImageWithCaption`
|
|
852
|
+
|
|
853
|
+
- kind: `composite`
|
|
854
|
+
- category: `image`
|
|
855
|
+
- internal: `false`
|
|
856
|
+
- children: `no`
|
|
857
|
+
- description: Image with a caption strip (top/bottom) or overlay caption
|
|
858
|
+
- llmGuidance: Use overlay for quotes/testimonials over photos. Use bottom for standard captions.
|
|
859
|
+
|
|
860
|
+
Props:
|
|
861
|
+
|
|
862
|
+
| Prop | Type | Required | Default | Notes |
|
|
863
|
+
| - | - | - | - | - |
|
|
864
|
+
| `caption` | string | yes | | maxLength=200 |
|
|
865
|
+
| `captionPosition` | enum("top" \| "bottom" \| "overlay") | yes | "bottom" | |
|
|
866
|
+
| `captionStyle` | object | no | | additionalProperties=false |
|
|
867
|
+
| `src` | string | yes | | minLength=1 |
|
|
868
|
+
|
|
869
|
+
##### `KenBurnsImage`
|
|
870
|
+
|
|
871
|
+
- kind: `composite`
|
|
872
|
+
- category: `image`
|
|
873
|
+
- internal: `false`
|
|
874
|
+
- children: `no`
|
|
875
|
+
- description: Slow zoom and pan (Ken Burns effect) for a still image
|
|
876
|
+
- llmGuidance: Classic documentary-style motion. startScale 1 -> endScale 1.2 is subtle; add panDirection for extra movement.
|
|
877
|
+
|
|
878
|
+
Props:
|
|
879
|
+
|
|
880
|
+
| Prop | Type | Required | Default | Notes |
|
|
881
|
+
| - | - | - | - | - |
|
|
882
|
+
| `endScale` | number | yes | 1.2 | min=1, max=2 |
|
|
883
|
+
| `panAmount` | number | yes | 50 | min=0, max=100 |
|
|
884
|
+
| `panDirection` | enum("none" \| "left" \| "right" \| "up" \| "down") | yes | "none" | |
|
|
885
|
+
| `src` | string | yes | | minLength=1 |
|
|
886
|
+
| `startScale` | number | yes | 1 | min=1, max=2 |
|
|
887
|
+
|
|
888
|
+
#### Category: `layout`
|
|
889
|
+
|
|
890
|
+
##### `Box`
|
|
891
|
+
|
|
892
|
+
- kind: `primitive`
|
|
893
|
+
- category: `layout`
|
|
894
|
+
- internal: `false`
|
|
895
|
+
- children: `yes`
|
|
896
|
+
- description: Positioned container box for layout and backgrounds
|
|
897
|
+
- llmGuidance: Use Box to position content by x/y and optionally constrain width/height. Prefer Box+Stack/Grid for layouts.
|
|
898
|
+
|
|
899
|
+
Props:
|
|
900
|
+
|
|
901
|
+
| Prop | Type | Required | Default | Notes |
|
|
902
|
+
| - | - | - | - | - |
|
|
903
|
+
| `backgroundColor` | string | no | | |
|
|
904
|
+
| `borderRadius` | number | yes | 0 | min=0 |
|
|
905
|
+
| `height` | number | no | | |
|
|
906
|
+
| `opacity` | number | yes | 1 | min=0, max=1 |
|
|
907
|
+
| `padding` | number | yes | 0 | min=0 |
|
|
908
|
+
| `width` | number | no | | |
|
|
909
|
+
| `x` | number | yes | 0 | |
|
|
910
|
+
| `y` | number | yes | 0 | |
|
|
911
|
+
|
|
912
|
+
##### `CardStack`
|
|
913
|
+
|
|
914
|
+
- kind: `composite`
|
|
915
|
+
- category: `layout`
|
|
916
|
+
- internal: `false`
|
|
917
|
+
- children: `no`
|
|
918
|
+
- description: Sequential stacked cards (2-5) with flip/slide/fade transitions
|
|
919
|
+
- llmGuidance: Use for steps/features. displayDuration is frames per card.
|
|
920
|
+
|
|
921
|
+
Props:
|
|
922
|
+
|
|
923
|
+
| Prop | Type | Required | Default | Notes |
|
|
924
|
+
| - | - | - | - | - |
|
|
925
|
+
| `cards` | array<object> | yes | | minItems=2, maxItems=5 |
|
|
926
|
+
| `displayDuration` | integer | yes | 90 | min=30, max=150 |
|
|
927
|
+
| `transition` | enum("flip" \| "slide" \| "fade") | yes | "flip" | |
|
|
928
|
+
|
|
929
|
+
##### `Grid`
|
|
930
|
+
|
|
931
|
+
- kind: `primitive`
|
|
932
|
+
- category: `layout`
|
|
933
|
+
- internal: `false`
|
|
934
|
+
- children: `yes`
|
|
935
|
+
- description: Grid layout container with configurable rows/columns
|
|
936
|
+
- llmGuidance: Use Grid for photo collages and dashboards. Provide exactly rows*columns children when possible.
|
|
937
|
+
|
|
938
|
+
Props:
|
|
939
|
+
|
|
940
|
+
| Prop | Type | Required | Default | Notes |
|
|
941
|
+
| - | - | - | - | - |
|
|
942
|
+
| `align` | enum("start" \| "center" \| "end" \| "stretch") | yes | "stretch" | |
|
|
943
|
+
| `columns` | integer | yes | 2 | min=1, max=12 |
|
|
944
|
+
| `gap` | number | yes | 24 | min=0 |
|
|
945
|
+
| `justify` | enum("start" \| "center" \| "end" \| "stretch") | yes | "stretch" | |
|
|
946
|
+
| `padding` | number | yes | 0 | min=0 |
|
|
947
|
+
| `rows` | integer | yes | 1 | min=1, max=12 |
|
|
948
|
+
|
|
949
|
+
##### `GridLayout`
|
|
950
|
+
|
|
951
|
+
- kind: `composite`
|
|
952
|
+
- category: `layout`
|
|
953
|
+
- internal: `false`
|
|
954
|
+
- children: `yes (1..∞)`
|
|
955
|
+
- description: Simple responsive grid layout for child components
|
|
956
|
+
- llmGuidance: Use for dashboards and collages. 2x2 is a good default for 4 items.
|
|
957
|
+
|
|
958
|
+
Props:
|
|
959
|
+
|
|
960
|
+
| Prop | Type | Required | Default | Notes |
|
|
961
|
+
| - | - | - | - | - |
|
|
962
|
+
| `columns` | integer | yes | 2 | min=1, max=4 |
|
|
963
|
+
| `gap` | number | yes | 20 | min=0, max=50 |
|
|
964
|
+
| `padding` | number | yes | 40 | min=0, max=100 |
|
|
965
|
+
| `rows` | integer | yes | 2 | min=1, max=4 |
|
|
966
|
+
|
|
967
|
+
##### `Scene`
|
|
968
|
+
|
|
969
|
+
- kind: `primitive`
|
|
970
|
+
- category: `layout`
|
|
971
|
+
- internal: `false`
|
|
972
|
+
- children: `yes`
|
|
973
|
+
- description: Scene container with a background and nested children
|
|
974
|
+
- llmGuidance: Use Scene to define a segment of the video. Scene timings must be sequential with no gaps. Put Text and Audio as children.
|
|
975
|
+
|
|
976
|
+
Props:
|
|
977
|
+
|
|
978
|
+
| Prop | Type | Required | Default | Notes |
|
|
979
|
+
| - | - | - | - | - |
|
|
980
|
+
| `background` | oneOf(object \| object \| object) | yes | | |
|
|
981
|
+
|
|
982
|
+
##### `Segment`
|
|
983
|
+
|
|
984
|
+
- kind: `primitive`
|
|
985
|
+
- category: `layout`
|
|
986
|
+
- internal: `true`
|
|
987
|
+
- children: `yes (1..1)`
|
|
988
|
+
- description: Internal segment wrapper (used by v2 segments compiler)
|
|
989
|
+
|
|
990
|
+
Props:
|
|
991
|
+
|
|
992
|
+
| Prop | Type | Required | Default | Notes |
|
|
993
|
+
| - | - | - | - | - |
|
|
994
|
+
| `enterTransition` | object | no | | additionalProperties=false |
|
|
995
|
+
| `exitTransition` | object | no | | additionalProperties=false |
|
|
996
|
+
|
|
997
|
+
##### `Shape`
|
|
382
998
|
|
|
383
|
-
-
|
|
384
|
-
-
|
|
999
|
+
- kind: `primitive`
|
|
1000
|
+
- category: `layout`
|
|
1001
|
+
- internal: `false`
|
|
1002
|
+
- children: `no`
|
|
1003
|
+
- description: Simple rect/circle shape for UI accents
|
|
1004
|
+
- llmGuidance: Use Shape for lines, badges, and simple UI blocks. Use circle for dots and rings.
|
|
385
1005
|
|
|
386
|
-
|
|
1006
|
+
Props:
|
|
1007
|
+
|
|
1008
|
+
| Prop | Type | Required | Default | Notes |
|
|
1009
|
+
| - | - | - | - | - |
|
|
1010
|
+
| `fill` | string | yes | "#FFFFFF" | |
|
|
1011
|
+
| `height` | number | yes | 100 | |
|
|
1012
|
+
| `opacity` | number | yes | 1 | min=0, max=1 |
|
|
1013
|
+
| `shape` | enum("rect" \| "circle") | yes | "rect" | |
|
|
1014
|
+
| `strokeColor` | string | no | | |
|
|
1015
|
+
| `strokeWidth` | number | yes | 0 | min=0 |
|
|
1016
|
+
| `width` | number | yes | 100 | |
|
|
1017
|
+
| `x` | number | yes | 0 | |
|
|
1018
|
+
| `y` | number | yes | 0 | |
|
|
1019
|
+
|
|
1020
|
+
##### `SplitScreen`
|
|
1021
|
+
|
|
1022
|
+
- kind: `composite`
|
|
1023
|
+
- category: `layout`
|
|
1024
|
+
- internal: `false`
|
|
1025
|
+
- children: `yes (2..2)`
|
|
1026
|
+
- description: Two-panel split screen layout
|
|
1027
|
+
- llmGuidance: Provide exactly 2 children. Use orientation="vertical" for left/right and "horizontal" for top/bottom.
|
|
1028
|
+
|
|
1029
|
+
Props:
|
|
1030
|
+
|
|
1031
|
+
| Prop | Type | Required | Default | Notes |
|
|
1032
|
+
| - | - | - | - | - |
|
|
1033
|
+
| `dividerColor` | string | no | | |
|
|
1034
|
+
| `gap` | number | yes | 48 | min=0 |
|
|
1035
|
+
| `orientation` | enum("vertical" \| "horizontal") | yes | "vertical" | |
|
|
1036
|
+
| `padding` | number | yes | 80 | min=0 |
|
|
1037
|
+
| `split` | number | yes | 0.5 | min=0.1, max=0.9 |
|
|
1038
|
+
|
|
1039
|
+
##### `Stack`
|
|
1040
|
+
|
|
1041
|
+
- kind: `primitive`
|
|
1042
|
+
- category: `layout`
|
|
1043
|
+
- internal: `false`
|
|
1044
|
+
- children: `yes`
|
|
1045
|
+
- description: Flexbox stack layout (row/column) with gap and alignment
|
|
1046
|
+
- llmGuidance: Use Stack to arrange child components in a row or column without manual positioning.
|
|
1047
|
+
|
|
1048
|
+
Props:
|
|
1049
|
+
|
|
1050
|
+
| Prop | Type | Required | Default | Notes |
|
|
1051
|
+
| - | - | - | - | - |
|
|
1052
|
+
| `align` | enum("start" \| "center" \| "end" \| "stretch") | yes | "center" | |
|
|
1053
|
+
| `direction` | enum("row" \| "column") | yes | "column" | |
|
|
1054
|
+
| `gap` | number | yes | 24 | min=0 |
|
|
1055
|
+
| `justify` | enum("start" \| "center" \| "end" \| "between") | yes | "center" | |
|
|
1056
|
+
| `padding` | number | yes | 0 | min=0 |
|
|
1057
|
+
|
|
1058
|
+
##### `ThirdLowerBanner`
|
|
1059
|
+
|
|
1060
|
+
- kind: `composite`
|
|
1061
|
+
- category: `layout`
|
|
1062
|
+
- internal: `false`
|
|
1063
|
+
- children: `no`
|
|
1064
|
+
- description: Broadcast-style lower-third banner with name/title and optional avatar
|
|
1065
|
+
- llmGuidance: Use for speaker introductions. name = big label, title = smaller subtitle. showAvatar + avatarSrc for profile image.
|
|
1066
|
+
|
|
1067
|
+
Props:
|
|
1068
|
+
|
|
1069
|
+
| Prop | Type | Required | Default | Notes |
|
|
1070
|
+
| - | - | - | - | - |
|
|
1071
|
+
| `accentColor` | string | yes | "#FF0000" | |
|
|
1072
|
+
| `avatarSrc` | string | no | | |
|
|
1073
|
+
| `backgroundColor` | string | yes | "rgba(0,0,0,0.8)" | |
|
|
1074
|
+
| `name` | string | yes | | maxLength=50 |
|
|
1075
|
+
| `primaryColor` | string | yes | "#FFFFFF" | |
|
|
1076
|
+
| `secondaryColor` | string | yes | "#CCCCCC" | |
|
|
1077
|
+
| `showAvatar` | boolean | yes | false | |
|
|
1078
|
+
| `title` | string | yes | | maxLength=100 |
|
|
1079
|
+
|
|
1080
|
+
#### Category: `media`
|
|
1081
|
+
|
|
1082
|
+
##### `Audio`
|
|
1083
|
+
|
|
1084
|
+
- kind: `primitive`
|
|
1085
|
+
- category: `media`
|
|
1086
|
+
- internal: `false`
|
|
1087
|
+
- children: `no`
|
|
1088
|
+
- description: Plays an audio file with optional trimming and fade in/out
|
|
1089
|
+
- llmGuidance: Use for background music or sound effects. Prefer short clips for SFX. Use fadeIn/fadeOut (in frames) for smoother audio starts/ends.
|
|
1090
|
+
|
|
1091
|
+
Props:
|
|
1092
|
+
|
|
1093
|
+
| Prop | Type | Required | Default | Notes |
|
|
1094
|
+
| - | - | - | - | - |
|
|
1095
|
+
| `fadeIn` | integer | yes | 0 | min=0, max=9007199254740991 |
|
|
1096
|
+
| `fadeOut` | integer | yes | 0 | min=0, max=9007199254740991 |
|
|
1097
|
+
| `src` | string | yes | | |
|
|
1098
|
+
| `startFrom` | integer | yes | 0 | min=0, max=9007199254740991 |
|
|
1099
|
+
| `volume` | number | yes | 1 | min=0, max=1 |
|
|
1100
|
+
|
|
1101
|
+
##### `Video`
|
|
1102
|
+
|
|
1103
|
+
- kind: `primitive`
|
|
1104
|
+
- category: `media`
|
|
1105
|
+
- internal: `false`
|
|
1106
|
+
- children: `no`
|
|
1107
|
+
- description: Full-frame video with object-fit options
|
|
1108
|
+
- llmGuidance: Use Video for B-roll. Keep videos short and muted unless you intentionally want audio.
|
|
1109
|
+
|
|
1110
|
+
Props:
|
|
1111
|
+
|
|
1112
|
+
| Prop | Type | Required | Default | Notes |
|
|
1113
|
+
| - | - | - | - | - |
|
|
1114
|
+
| `borderRadius` | number | yes | 0 | min=0 |
|
|
1115
|
+
| `fit` | enum("cover" \| "contain") | yes | "cover" | |
|
|
1116
|
+
| `muted` | boolean | yes | true | |
|
|
1117
|
+
| `opacity` | number | yes | 1 | min=0, max=1 |
|
|
1118
|
+
| `src` | string | yes | | minLength=1 |
|
|
1119
|
+
|
|
1120
|
+
##### `VideoWithOverlay`
|
|
1121
|
+
|
|
1122
|
+
- kind: `composite`
|
|
1123
|
+
- category: `media`
|
|
1124
|
+
- internal: `false`
|
|
1125
|
+
- children: `no`
|
|
1126
|
+
- description: Video background with an optional overlay (text/logo/gradient)
|
|
1127
|
+
- llmGuidance: Use gradient overlay to improve text readability. Set volume=0 to mute.
|
|
1128
|
+
|
|
1129
|
+
Props:
|
|
1130
|
+
|
|
1131
|
+
| Prop | Type | Required | Default | Notes |
|
|
1132
|
+
| - | - | - | - | - |
|
|
1133
|
+
| `overlay` | object | no | | additionalProperties=false |
|
|
1134
|
+
| `playbackRate` | number | yes | 1 | min=0.5, max=2 |
|
|
1135
|
+
| `src` | string | yes | | minLength=1 |
|
|
1136
|
+
| `volume` | number | yes | 1 | min=0, max=1 |
|
|
1137
|
+
|
|
1138
|
+
#### Category: `social`
|
|
1139
|
+
|
|
1140
|
+
##### `InstagramStory`
|
|
1141
|
+
|
|
1142
|
+
- kind: `composite`
|
|
1143
|
+
- category: `social`
|
|
1144
|
+
- internal: `false`
|
|
1145
|
+
- children: `no`
|
|
1146
|
+
- description: Instagram story-style layout with profile header, text overlay, and optional sticker
|
|
1147
|
+
- llmGuidance: Best with 1080x1920 (9:16). Use backgroundImage + short text + optional sticker for mobile-style content.
|
|
1148
|
+
|
|
1149
|
+
Props:
|
|
1150
|
+
|
|
1151
|
+
| Prop | Type | Required | Default | Notes |
|
|
1152
|
+
| - | - | - | - | - |
|
|
1153
|
+
| `backgroundColor` | string | yes | "#000000" | |
|
|
1154
|
+
| `backgroundImage` | string | no | | |
|
|
1155
|
+
| `musicTrack` | string | no | | |
|
|
1156
|
+
| `profilePic` | string | no | | |
|
|
1157
|
+
| `sticker` | enum("none" \| "poll" \| "question" \| "countdown") | yes | "none" | |
|
|
1158
|
+
| `text` | string | no | | maxLength=100 |
|
|
1159
|
+
| `username` | string | no | | |
|
|
1160
|
+
|
|
1161
|
+
##### `TikTokCaption`
|
|
1162
|
+
|
|
1163
|
+
- kind: `composite`
|
|
1164
|
+
- category: `social`
|
|
1165
|
+
- internal: `false`
|
|
1166
|
+
- children: `no`
|
|
1167
|
+
- description: TikTok-style captions with stroke and optional word highlighting
|
|
1168
|
+
- llmGuidance: Always keep strokeWidth>=2 for readability. highlightStyle="word" or "bounce" makes captions feel dynamic.
|
|
1169
|
+
|
|
1170
|
+
Props:
|
|
1171
|
+
|
|
1172
|
+
| Prop | Type | Required | Default | Notes |
|
|
1173
|
+
| - | - | - | - | - |
|
|
1174
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1175
|
+
| `fontSize` | number | yes | 48 | min=12, max=120 |
|
|
1176
|
+
| `highlightStyle` | enum("word" \| "bounce" \| "none") | yes | "word" | |
|
|
1177
|
+
| `position` | enum("center" \| "bottom") | yes | "center" | |
|
|
1178
|
+
| `strokeColor` | string | yes | "#000000" | |
|
|
1179
|
+
| `strokeWidth` | number | yes | 3 | min=0, max=10 |
|
|
1180
|
+
| `text` | string | yes | | maxLength=150 |
|
|
1181
|
+
|
|
1182
|
+
##### `TwitterCard`
|
|
1183
|
+
|
|
1184
|
+
- kind: `composite`
|
|
1185
|
+
- category: `social`
|
|
1186
|
+
- internal: `false`
|
|
1187
|
+
- children: `no`
|
|
1188
|
+
- description: Twitter/X post card layout with author header and optional image
|
|
1189
|
+
- llmGuidance: Use for announcements/testimonials. Keep tweet short for readability.
|
|
1190
|
+
|
|
1191
|
+
Props:
|
|
1192
|
+
|
|
1193
|
+
| Prop | Type | Required | Default | Notes |
|
|
1194
|
+
| - | - | - | - | - |
|
|
1195
|
+
| `author` | string | yes | | minLength=1 |
|
|
1196
|
+
| `avatarSrc` | string | no | | |
|
|
1197
|
+
| `handle` | string | yes | | minLength=1 |
|
|
1198
|
+
| `image` | string | no | | |
|
|
1199
|
+
| `timestamp` | string | no | | |
|
|
1200
|
+
| `tweet` | string | yes | | maxLength=280 |
|
|
1201
|
+
| `verified` | boolean | yes | false | |
|
|
1202
|
+
|
|
1203
|
+
##### `YouTubeThumbnail`
|
|
1204
|
+
|
|
1205
|
+
- kind: `composite`
|
|
1206
|
+
- category: `social`
|
|
1207
|
+
- internal: `false`
|
|
1208
|
+
- children: `no`
|
|
1209
|
+
- description: YouTube-style thumbnail layout (16:9) with bold title and optional face cutout
|
|
1210
|
+
- llmGuidance: Use 1280x720 video size. Keep title short and high-contrast. style="bold" is classic thumbnail.
|
|
1211
|
+
|
|
1212
|
+
Props:
|
|
1213
|
+
|
|
1214
|
+
| Prop | Type | Required | Default | Notes |
|
|
1215
|
+
| - | - | - | - | - |
|
|
1216
|
+
| `accentColor` | string | yes | "#FF0000" | |
|
|
1217
|
+
| `backgroundImage` | string | yes | | minLength=1 |
|
|
1218
|
+
| `style` | enum("bold" \| "minimal" \| "dramatic") | yes | "bold" | |
|
|
1219
|
+
| `subtitle` | string | no | | maxLength=40 |
|
|
1220
|
+
| `thumbnailFace` | string | no | | |
|
|
1221
|
+
| `title` | string | yes | | maxLength=60 |
|
|
1222
|
+
|
|
1223
|
+
#### Category: `text`
|
|
1224
|
+
|
|
1225
|
+
##### `CountUpText`
|
|
1226
|
+
|
|
1227
|
+
- kind: `composite`
|
|
1228
|
+
- category: `text`
|
|
1229
|
+
- internal: `false`
|
|
1230
|
+
- children: `no`
|
|
1231
|
+
- description: Counts from a start value to an end value with formatting options
|
|
1232
|
+
- llmGuidance: Use for metrics. format="currency" adds $, format="percentage" multiplies by 100 and adds %.
|
|
1233
|
+
|
|
1234
|
+
Props:
|
|
1235
|
+
|
|
1236
|
+
| Prop | Type | Required | Default | Notes |
|
|
1237
|
+
| - | - | - | - | - |
|
|
1238
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1239
|
+
| `decimals` | integer | yes | 0 | min=0, max=4 |
|
|
1240
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1241
|
+
| `fontSize` | number | yes | 72 | min=8, max=240 |
|
|
1242
|
+
| `fontWeight` | integer | yes | 700 | min=100, max=900 |
|
|
1243
|
+
| `format` | enum("integer" \| "decimal" \| "currency" \| "percentage") | yes | "integer" | |
|
|
1244
|
+
| `from` | number | yes | 0 | |
|
|
1245
|
+
| `position` | enum("top" \| "center" \| "bottom") | yes | "center" | |
|
|
1246
|
+
| `prefix` | string | no | | |
|
|
1247
|
+
| `suffix` | string | no | | |
|
|
1248
|
+
| `to` | number | yes | 100 | |
|
|
1249
|
+
|
|
1250
|
+
##### `GlitchText`
|
|
1251
|
+
|
|
1252
|
+
- kind: `composite`
|
|
1253
|
+
- category: `text`
|
|
1254
|
+
- internal: `false`
|
|
1255
|
+
- children: `no`
|
|
1256
|
+
- description: Cyberpunk-style glitch text with RGB split jitter
|
|
1257
|
+
- llmGuidance: Use for tech/error moments. intensity 1-3 subtle, 7-10 extreme. glitchDuration is frames at start.
|
|
1258
|
+
|
|
1259
|
+
Props:
|
|
1260
|
+
|
|
1261
|
+
| Prop | Type | Required | Default | Notes |
|
|
1262
|
+
| - | - | - | - | - |
|
|
1263
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1264
|
+
| `content` | string | yes | | maxLength=100 |
|
|
1265
|
+
| `fontFamily` | string | yes | "monospace" | |
|
|
1266
|
+
| `fontSize` | number | yes | 72 | min=8, max=240 |
|
|
1267
|
+
| `glitchDuration` | integer | yes | 10 | min=5, max=30 |
|
|
1268
|
+
| `intensity` | integer | yes | 5 | min=1, max=10 |
|
|
1269
|
+
| `position` | enum("top" \| "center" \| "bottom") | yes | "center" | |
|
|
1270
|
+
|
|
1271
|
+
##### `KineticTypography`
|
|
1272
|
+
|
|
1273
|
+
- kind: `composite`
|
|
1274
|
+
- category: `text`
|
|
1275
|
+
- internal: `false`
|
|
1276
|
+
- children: `no`
|
|
1277
|
+
- description: Rhythmic single-word kinetic typography driven by a timing array
|
|
1278
|
+
- llmGuidance: Provide timing frames for when each word appears. Use emphasis="giant" sparingly for impact.
|
|
1279
|
+
|
|
1280
|
+
Props:
|
|
1281
|
+
|
|
1282
|
+
| Prop | Type | Required | Default | Notes |
|
|
1283
|
+
| - | - | - | - | - |
|
|
1284
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1285
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1286
|
+
| `fontSize` | number | yes | 48 | min=12, max=140 |
|
|
1287
|
+
| `timing` | array<integer> | yes | | minItems=1 |
|
|
1288
|
+
| `transition` | enum("fade" \| "scale" \| "slideLeft" \| "slideRight") | yes | "scale" | |
|
|
1289
|
+
| `words` | array<object> | yes | | minItems=1, maxItems=50 |
|
|
387
1290
|
|
|
388
|
-
|
|
1291
|
+
##### `OutlineText`
|
|
1292
|
+
|
|
1293
|
+
- kind: `composite`
|
|
1294
|
+
- category: `text`
|
|
1295
|
+
- internal: `false`
|
|
1296
|
+
- children: `no`
|
|
1297
|
+
- description: Outlined title text with simple draw/fill animation
|
|
1298
|
+
- llmGuidance: Use for bold titles. animation="draw" emphasizes the outline; animation="fill" reveals the fill color.
|
|
1299
|
+
|
|
1300
|
+
Props:
|
|
1301
|
+
|
|
1302
|
+
| Prop | Type | Required | Default | Notes |
|
|
1303
|
+
| - | - | - | - | - |
|
|
1304
|
+
| `animation` | enum("draw" \| "fill") | yes | "draw" | |
|
|
1305
|
+
| `content` | string | yes | | maxLength=50 |
|
|
1306
|
+
| `fillColor` | string | yes | "#000000" | |
|
|
1307
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1308
|
+
| `fontSize` | number | yes | 96 | min=8, max=240 |
|
|
1309
|
+
| `fontWeight` | integer | yes | 800 | min=100, max=900 |
|
|
1310
|
+
| `outlineColor` | string | yes | "#FFFFFF" | |
|
|
1311
|
+
| `position` | enum("top" \| "center" \| "bottom") | yes | "center" | |
|
|
1312
|
+
| `strokeWidth` | number | yes | 3 | min=1, max=10 |
|
|
1313
|
+
|
|
1314
|
+
##### `SplitText`
|
|
1315
|
+
|
|
1316
|
+
- kind: `composite`
|
|
1317
|
+
- category: `text`
|
|
1318
|
+
- internal: `false`
|
|
1319
|
+
- children: `no`
|
|
1320
|
+
- description: Animated text where each word or letter enters with a staggered effect
|
|
1321
|
+
- llmGuidance: Use for titles. splitBy="word" is best for phrases; splitBy="letter" is dramatic for short words.
|
|
1322
|
+
|
|
1323
|
+
Props:
|
|
1324
|
+
|
|
1325
|
+
| Prop | Type | Required | Default | Notes |
|
|
1326
|
+
| - | - | - | - | - |
|
|
1327
|
+
| `animation` | enum("fade" \| "slideUp" \| "slideDown" \| "scale" \| "rotate") | yes | "slideUp" | |
|
|
1328
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1329
|
+
| `content` | string | yes | | maxLength=200 |
|
|
1330
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1331
|
+
| `fontSize` | number | yes | 48 | min=8, max=200 |
|
|
1332
|
+
| `position` | enum("top" \| "center" \| "bottom") | yes | "center" | |
|
|
1333
|
+
| `splitBy` | enum("word" \| "letter") | yes | "word" | |
|
|
1334
|
+
| `stagger` | integer | yes | 3 | min=1, max=10 |
|
|
1335
|
+
|
|
1336
|
+
##### `SubtitleText`
|
|
1337
|
+
|
|
1338
|
+
- kind: `composite`
|
|
1339
|
+
- category: `text`
|
|
1340
|
+
- internal: `false`
|
|
1341
|
+
- children: `no`
|
|
1342
|
+
- description: Caption/subtitle box with fade in/out and optional highlighted words
|
|
1343
|
+
- llmGuidance: Use for narration/captions. highlightWords helps emphasize key terms.
|
|
1344
|
+
|
|
1345
|
+
Props:
|
|
1346
|
+
|
|
1347
|
+
| Prop | Type | Required | Default | Notes |
|
|
1348
|
+
| - | - | - | - | - |
|
|
1349
|
+
| `backgroundColor` | string | yes | "rgba(0,0,0,0.7)" | |
|
|
1350
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1351
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1352
|
+
| `fontSize` | number | yes | 36 | min=12, max=80 |
|
|
1353
|
+
| `highlightWords` | array<string> | no | | |
|
|
1354
|
+
| `maxWidth` | number | yes | 800 | min=200, max=1200 |
|
|
1355
|
+
| `padding` | number | yes | 20 | min=0, max=80 |
|
|
1356
|
+
| `position` | enum("top" \| "bottom") | yes | "bottom" | |
|
|
1357
|
+
| `text` | string | yes | | maxLength=200 |
|
|
1358
|
+
|
|
1359
|
+
##### `Text`
|
|
1360
|
+
|
|
1361
|
+
- kind: `primitive`
|
|
1362
|
+
- category: `text`
|
|
1363
|
+
- internal: `false`
|
|
1364
|
+
- children: `no`
|
|
1365
|
+
- description: Displays animated text with positioning and animation options
|
|
1366
|
+
- llmGuidance: Use for titles, subtitles, captions. Keep content under 100 characters for readability. Position "center" works best for titles.
|
|
1367
|
+
|
|
1368
|
+
Props:
|
|
1369
|
+
|
|
1370
|
+
| Prop | Type | Required | Default | Notes |
|
|
1371
|
+
| - | - | - | - | - |
|
|
1372
|
+
| `animation` | enum("none" \| "fade" \| "slide" \| "zoom") | yes | "fade" | |
|
|
1373
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1374
|
+
| `content` | string | yes | | |
|
|
1375
|
+
| `fontSize` | number | yes | 48 | |
|
|
1376
|
+
| `position` | enum("top" \| "center" \| "bottom" \| "left" \| "right") | yes | "center" | |
|
|
1377
|
+
|
|
1378
|
+
##### `TypewriterText`
|
|
1379
|
+
|
|
1380
|
+
- kind: `composite`
|
|
1381
|
+
- category: `text`
|
|
1382
|
+
- internal: `false`
|
|
1383
|
+
- children: `no`
|
|
1384
|
+
- description: Character-by-character text reveal with optional blinking cursor
|
|
1385
|
+
- llmGuidance: Use for dramatic reveals and terminal-style text. speed ~1-2 is readable; 3-5 is fast.
|
|
1386
|
+
|
|
1387
|
+
Props:
|
|
1388
|
+
|
|
1389
|
+
| Prop | Type | Required | Default | Notes |
|
|
1390
|
+
| - | - | - | - | - |
|
|
1391
|
+
| `color` | string | yes | "#FFFFFF" | |
|
|
1392
|
+
| `content` | string | yes | | maxLength=500 |
|
|
1393
|
+
| `cursorColor` | string | yes | "#FFFFFF" | |
|
|
1394
|
+
| `fontFamily` | string | yes | "Inter" | |
|
|
1395
|
+
| `fontSize` | number | yes | 48 | min=8, max=200 |
|
|
1396
|
+
| `position` | enum("top" \| "center" \| "bottom") | yes | "center" | |
|
|
1397
|
+
| `showCursor` | boolean | yes | true | |
|
|
1398
|
+
| `speed` | number | yes | 2 | min=0.5, max=5 |
|
|
1399
|
+
|
|
1400
|
+
#### Category: `transition`
|
|
1401
|
+
|
|
1402
|
+
##### `CircularReveal`
|
|
1403
|
+
|
|
1404
|
+
- kind: `composite`
|
|
1405
|
+
- category: `transition`
|
|
1406
|
+
- internal: `false`
|
|
1407
|
+
- children: `yes (1..∞)`
|
|
1408
|
+
- description: Circular iris reveal/hide transition wrapper
|
|
1409
|
+
- llmGuidance: direction="open" reveals from center, direction="close" hides to a point. center controls origin.
|
|
1410
|
+
|
|
1411
|
+
Props:
|
|
1412
|
+
|
|
1413
|
+
| Prop | Type | Required | Default | Notes |
|
|
1414
|
+
| - | - | - | - | - |
|
|
1415
|
+
| `center` | object | no | | additionalProperties=false |
|
|
1416
|
+
| `direction` | enum("open" \| "close") | yes | "open" | |
|
|
1417
|
+
| `durationInFrames` | integer | yes | 30 | min=10, max=60 |
|
|
1418
|
+
| `phase` | enum("in" \| "out" \| "inOut") | yes | "inOut" | |
|
|
1419
|
+
|
|
1420
|
+
##### `FadeTransition`
|
|
1421
|
+
|
|
1422
|
+
- kind: `composite`
|
|
1423
|
+
- category: `transition`
|
|
1424
|
+
- internal: `false`
|
|
1425
|
+
- children: `yes (1..∞)`
|
|
1426
|
+
- description: Fade in/out wrapper (used for segment transitions and overlays)
|
|
1427
|
+
- llmGuidance: Use for gentle transitions. durationInFrames ~30 is standard.
|
|
1428
|
+
|
|
1429
|
+
Props:
|
|
1430
|
+
|
|
1431
|
+
| Prop | Type | Required | Default | Notes |
|
|
1432
|
+
| - | - | - | - | - |
|
|
1433
|
+
| `durationInFrames` | integer | yes | 30 | min=10, max=60 |
|
|
1434
|
+
| `easing` | enum("linear" \| "easeIn" \| "easeOut" \| "easeInOut") | yes | "easeInOut" | |
|
|
1435
|
+
| `phase` | enum("in" \| "out" \| "inOut") | yes | "inOut" | |
|
|
1436
|
+
|
|
1437
|
+
##### `SlideTransition`
|
|
1438
|
+
|
|
1439
|
+
- kind: `composite`
|
|
1440
|
+
- category: `transition`
|
|
1441
|
+
- internal: `false`
|
|
1442
|
+
- children: `yes (1..∞)`
|
|
1443
|
+
- description: Slide in/out wrapper (used for segment transitions and overlays)
|
|
1444
|
+
- llmGuidance: Use for more dynamic transitions. direction controls where content enters from.
|
|
1445
|
+
|
|
1446
|
+
Props:
|
|
1447
|
+
|
|
1448
|
+
| Prop | Type | Required | Default | Notes |
|
|
1449
|
+
| - | - | - | - | - |
|
|
1450
|
+
| `direction` | enum("left" \| "right" \| "up" \| "down") | yes | "left" | |
|
|
1451
|
+
| `distance` | integer | yes | 160 | min=1, max=2000 |
|
|
1452
|
+
| `durationInFrames` | integer | yes | 30 | min=10, max=60 |
|
|
1453
|
+
| `phase` | enum("in" \| "out" \| "inOut") | yes | "inOut" | |
|
|
1454
|
+
|
|
1455
|
+
##### `WipeTransition`
|
|
1456
|
+
|
|
1457
|
+
- kind: `composite`
|
|
1458
|
+
- category: `transition`
|
|
1459
|
+
- internal: `false`
|
|
1460
|
+
- children: `yes (1..∞)`
|
|
1461
|
+
- description: Directional wipe reveal/hide wrapper transition
|
|
1462
|
+
- llmGuidance: Use as a more stylized reveal. softEdge can make it feel less harsh.
|
|
1463
|
+
|
|
1464
|
+
Props:
|
|
1465
|
+
|
|
1466
|
+
| Prop | Type | Required | Default | Notes |
|
|
1467
|
+
| - | - | - | - | - |
|
|
1468
|
+
| `direction` | enum("left" \| "right" \| "up" \| "down" \| "diagonal") | yes | "right" | |
|
|
1469
|
+
| `durationInFrames` | integer | yes | 30 | min=10, max=60 |
|
|
1470
|
+
| `phase` | enum("in" \| "out" \| "inOut") | yes | "inOut" | |
|
|
1471
|
+
| `softEdge` | boolean | yes | false | |
|
|
1472
|
+
|
|
1473
|
+
##### `ZoomTransition`
|
|
1474
|
+
|
|
1475
|
+
- kind: `composite`
|
|
1476
|
+
- category: `transition`
|
|
1477
|
+
- internal: `false`
|
|
1478
|
+
- children: `yes (1..∞)`
|
|
1479
|
+
- description: Zoom in/out wrapper transition
|
|
1480
|
+
- llmGuidance: Use for energetic cuts. type="zoomIn" feels punchy; type="zoomOut" feels calmer.
|
|
1481
|
+
|
|
1482
|
+
Props:
|
|
1483
|
+
|
|
1484
|
+
| Prop | Type | Required | Default | Notes |
|
|
1485
|
+
| - | - | - | - | - |
|
|
1486
|
+
| `durationInFrames` | integer | yes | 30 | min=10, max=60 |
|
|
1487
|
+
| `phase` | enum("in" \| "out" \| "inOut") | yes | "inOut" | |
|
|
1488
|
+
| `type` | enum("zoomIn" \| "zoomOut") | yes | "zoomIn" | |
|
|
1489
|
+
|
|
1490
|
+
<!-- END GENERATED: COMPONENTS -->
|
|
1491
|
+
|
|
1492
|
+
## Examples (composition recipes)
|
|
1493
|
+
|
|
1494
|
+
The `examples/` directory contains validated IR JSON files you can use as starting points.
|
|
1495
|
+
|
|
1496
|
+
Quick validate:
|
|
1497
|
+
|
|
1498
|
+
```bash
|
|
1499
|
+
npx waves validate --in examples/basic.v2.json
|
|
1500
|
+
```
|
|
1501
|
+
|
|
1502
|
+
Validate all examples:
|
|
1503
|
+
|
|
1504
|
+
```powershell
|
|
1505
|
+
Get-ChildItem examples -Filter *.v2.json | ForEach-Object { npx waves validate --in $_.FullName }
|
|
1506
|
+
```
|
|
1507
|
+
|
|
1508
|
+
Render (writes an MP4; `examples/*.mp4` are gitignored):
|
|
1509
|
+
|
|
1510
|
+
```bash
|
|
1511
|
+
npx waves render --in examples/basic.v2.json --out examples/basic.v2.mp4 --codec h264 --crf 28 --concurrency 1
|
|
1512
|
+
```
|
|
1513
|
+
|
|
1514
|
+
If an example references `/assets/...`, you must provide those files and pass `--publicDir`:
|
|
1515
|
+
|
|
1516
|
+
```bash
|
|
1517
|
+
npx waves render --in examples/intro-stats-outro.v2.json --out examples/intro-stats-outro.v2.mp4 --publicDir ./public
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
### Example 0: basic starter (segments + composites)
|
|
1521
|
+
|
|
1522
|
+
File: `examples/basic.v2.json`
|
|
1523
|
+
|
|
1524
|
+
This is the default starter IR used by `waves write-ir --template basic`. It demonstrates:
|
|
1525
|
+
|
|
1526
|
+
- segments mode (recommended)
|
|
1527
|
+
- a segment-to-segment overlap via `transitionToNext`
|
|
1528
|
+
- text composites (`SplitText`, `TypewriterText`)
|
|
1529
|
+
- an overlay composite (`Watermark`)
|
|
1530
|
+
|
|
1531
|
+
```json
|
|
1532
|
+
{
|
|
1533
|
+
"version": "2.0",
|
|
1534
|
+
"video": {
|
|
1535
|
+
"id": "main",
|
|
1536
|
+
"width": 1920,
|
|
1537
|
+
"height": 1080,
|
|
1538
|
+
"fps": 30,
|
|
1539
|
+
"durationInFrames": 165
|
|
1540
|
+
},
|
|
1541
|
+
"segments": [
|
|
1542
|
+
{
|
|
1543
|
+
"id": "scene-1",
|
|
1544
|
+
"durationInFrames": 90,
|
|
1545
|
+
"transitionToNext": {
|
|
1546
|
+
"type": "FadeTransition",
|
|
1547
|
+
"durationInFrames": 15
|
|
1548
|
+
},
|
|
1549
|
+
"root": {
|
|
1550
|
+
"id": "root",
|
|
1551
|
+
"type": "Scene",
|
|
1552
|
+
"props": {
|
|
1553
|
+
"background": { "type": "color", "value": "#000000" }
|
|
1554
|
+
},
|
|
1555
|
+
"children": [
|
|
1556
|
+
{
|
|
1557
|
+
"id": "title",
|
|
1558
|
+
"type": "SplitText",
|
|
1559
|
+
"props": {
|
|
1560
|
+
"content": "Waves v0.2.0",
|
|
1561
|
+
"fontSize": 96,
|
|
1562
|
+
"splitBy": "word",
|
|
1563
|
+
"stagger": 3,
|
|
1564
|
+
"animation": "slideUp"
|
|
1565
|
+
}
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
"id": "subtitle",
|
|
1569
|
+
"type": "TypewriterText",
|
|
1570
|
+
"props": {
|
|
1571
|
+
"content": "Composite components + hybrid IR",
|
|
1572
|
+
"fontSize": 48,
|
|
1573
|
+
"position": "bottom",
|
|
1574
|
+
"speed": 1.5
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
"id": "wm",
|
|
1579
|
+
"type": "Watermark",
|
|
1580
|
+
"props": {
|
|
1581
|
+
"type": "text",
|
|
1582
|
+
"text": "@depths.ai",
|
|
1583
|
+
"position": "bottomRight",
|
|
1584
|
+
"opacity": 0.4,
|
|
1585
|
+
"size": 60
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
]
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
"id": "scene-2",
|
|
1593
|
+
"durationInFrames": 90,
|
|
1594
|
+
"root": {
|
|
1595
|
+
"id": "root-2",
|
|
1596
|
+
"type": "Scene",
|
|
1597
|
+
"props": {
|
|
1598
|
+
"background": { "type": "color", "value": "#0B1220" }
|
|
1599
|
+
},
|
|
1600
|
+
"children": [
|
|
1601
|
+
{
|
|
1602
|
+
"id": "lower-third",
|
|
1603
|
+
"type": "ThirdLowerBanner",
|
|
1604
|
+
"props": { "name": "Waves", "title": "v0.2.0 - Composites + transitions", "accentColor": "#3B82F6" }
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
"id": "count",
|
|
1608
|
+
"type": "AnimatedCounter",
|
|
1609
|
+
"props": { "from": 0, "to": 35, "suffix": " components", "fontSize": 96, "color": "#FFFFFF" }
|
|
1610
|
+
},
|
|
1611
|
+
{
|
|
1612
|
+
"id": "wm-2",
|
|
1613
|
+
"type": "Watermark",
|
|
1614
|
+
"props": { "type": "text", "text": "waves", "position": "topLeft", "opacity": 0.25, "size": 52 }
|
|
1615
|
+
}
|
|
1616
|
+
]
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
]
|
|
1620
|
+
}
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
### Example 1: primitives-only layout
|
|
1624
|
+
|
|
1625
|
+
File: `examples/primitives-card.v2.json`
|
|
1626
|
+
|
|
1627
|
+
This example uses only primitives: `Scene`, `Box`, `Grid`, `Shape`, `Text`.
|
|
1628
|
+
|
|
1629
|
+
```json
|
|
1630
|
+
{
|
|
1631
|
+
"version": "2.0",
|
|
1632
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 150 },
|
|
1633
|
+
"segments": [
|
|
1634
|
+
{
|
|
1635
|
+
"id": "card",
|
|
1636
|
+
"durationInFrames": 150,
|
|
1637
|
+
"root": {
|
|
1638
|
+
"id": "scene",
|
|
1639
|
+
"type": "Scene",
|
|
1640
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
1641
|
+
"children": [
|
|
1642
|
+
{ "id": "accent", "type": "Shape", "props": { "shape": "rect", "x": 180, "y": 200, "width": 12, "height": 680, "fill": "#3B82F6", "opacity": 1 } },
|
|
1643
|
+
{
|
|
1644
|
+
"id": "panel",
|
|
1645
|
+
"type": "Box",
|
|
1646
|
+
"props": { "x": 192, "y": 200, "width": 1548, "height": 680, "padding": 0, "backgroundColor": "rgba(255,255,255,0.06)", "borderRadius": 36, "opacity": 1 },
|
|
1647
|
+
"children": [
|
|
1648
|
+
{
|
|
1649
|
+
"id": "panel-grid",
|
|
1650
|
+
"type": "Grid",
|
|
1651
|
+
"props": { "columns": 2, "rows": 2, "gap": 28, "padding": 64, "align": "stretch", "justify": "stretch" },
|
|
1652
|
+
"children": [
|
|
1653
|
+
{
|
|
1654
|
+
"id": "tile-1",
|
|
1655
|
+
"type": "Box",
|
|
1656
|
+
"props": { "x": 0, "y": 0, "padding": 40, "backgroundColor": "rgba(255,255,255,0.07)", "borderRadius": 24 },
|
|
1657
|
+
"children": [
|
|
1658
|
+
{ "id": "t1", "type": "Text", "props": { "content": "Primitives", "fontSize": 64, "position": "center", "animation": "fade" } }
|
|
1659
|
+
]
|
|
1660
|
+
},
|
|
1661
|
+
{
|
|
1662
|
+
"id": "tile-2",
|
|
1663
|
+
"type": "Box",
|
|
1664
|
+
"props": { "x": 0, "y": 0, "padding": 40, "backgroundColor": "rgba(255,255,255,0.07)", "borderRadius": 24 },
|
|
1665
|
+
"children": [
|
|
1666
|
+
{ "id": "t2", "type": "Text", "props": { "content": "Box + Grid", "fontSize": 54, "position": "center", "animation": "slide" } }
|
|
1667
|
+
]
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
"id": "tile-3",
|
|
1671
|
+
"type": "Box",
|
|
1672
|
+
"props": { "x": 0, "y": 0, "padding": 40, "backgroundColor": "rgba(255,255,255,0.07)", "borderRadius": 24 },
|
|
1673
|
+
"children": [
|
|
1674
|
+
{ "id": "t3", "type": "Text", "props": { "content": "Shape", "fontSize": 54, "position": "center", "animation": "zoom" } }
|
|
1675
|
+
]
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
"id": "tile-4",
|
|
1679
|
+
"type": "Box",
|
|
1680
|
+
"props": { "x": 0, "y": 0, "padding": 40, "backgroundColor": "rgba(255,255,255,0.07)", "borderRadius": 24 },
|
|
1681
|
+
"children": [
|
|
1682
|
+
{ "id": "t4", "type": "Text", "props": { "content": "Text", "fontSize": 54, "position": "center", "animation": "fade" } }
|
|
1683
|
+
]
|
|
1684
|
+
}
|
|
1685
|
+
]
|
|
1686
|
+
}
|
|
1687
|
+
]
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
"id": "footer",
|
|
1691
|
+
"type": "Text",
|
|
1692
|
+
"props": {
|
|
1693
|
+
"content": "This entire layout is built from primitives only.",
|
|
1694
|
+
"fontSize": 34,
|
|
1695
|
+
"position": "bottom",
|
|
1696
|
+
"animation": "fade",
|
|
1697
|
+
"color": "#C7D2FE"
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
]
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
]
|
|
1704
|
+
}
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
### Segment overlap math (how Example 2 works)
|
|
1708
|
+
|
|
1709
|
+
When authoring segments with overlaps, the total end time is:
|
|
1710
|
+
|
|
1711
|
+
`sum(segments[i].durationInFrames) - sum(segments[i].transitionToNext.durationInFrames)`
|
|
1712
|
+
|
|
1713
|
+
Example 2 has 4 segments:
|
|
1714
|
+
|
|
1715
|
+
- segment durations: 90 + 90 + 90 + 90 = 360
|
|
1716
|
+
- overlap durations: 15 + 15 + 15 = 45
|
|
1717
|
+
- video duration: 360 - 45 = 315 (must match `video.durationInFrames`)
|
|
1718
|
+
|
|
1719
|
+
### Example 2: segment transitions showcase
|
|
1720
|
+
|
|
1721
|
+
File: `examples/transitions-showcase.v2.json`
|
|
1722
|
+
|
|
1723
|
+
This example shows how to chain segments with overlaps using `transitionToNext`.
|
|
1724
|
+
|
|
1725
|
+
```json
|
|
1726
|
+
{
|
|
1727
|
+
"version": "2.0",
|
|
1728
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 315 },
|
|
1729
|
+
"segments": [
|
|
1730
|
+
{
|
|
1731
|
+
"id": "s1",
|
|
1732
|
+
"durationInFrames": 90,
|
|
1733
|
+
"transitionToNext": { "type": "FadeTransition", "durationInFrames": 15 },
|
|
1734
|
+
"root": {
|
|
1735
|
+
"id": "scene-1",
|
|
1736
|
+
"type": "Scene",
|
|
1737
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
1738
|
+
"children": [
|
|
1739
|
+
{ "id": "title-1", "type": "SplitText", "props": { "content": "FadeTransition", "fontSize": 100, "splitBy": "word", "stagger": 3, "animation": "slideUp", "position": "center" } },
|
|
1740
|
+
{ "id": "wm-1", "type": "Watermark", "props": { "type": "text", "text": "waves", "position": "bottomRight", "opacity": 0.35, "size": 60 } }
|
|
1741
|
+
]
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"id": "s2",
|
|
1746
|
+
"durationInFrames": 90,
|
|
1747
|
+
"transitionToNext": { "type": "SlideTransition", "durationInFrames": 15, "props": { "direction": "left", "distance": 120 } },
|
|
1748
|
+
"root": {
|
|
1749
|
+
"id": "scene-2",
|
|
1750
|
+
"type": "Scene",
|
|
1751
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
1752
|
+
"children": [
|
|
1753
|
+
{ "id": "title-2", "type": "SplitText", "props": { "content": "SlideTransition", "fontSize": 96, "splitBy": "word", "stagger": 3, "animation": "slideUp", "position": "center" } },
|
|
1754
|
+
{ "id": "subtitle-2", "type": "TypewriterText", "props": { "content": "direction=left, distance=120", "fontSize": 44, "position": "bottom", "speed": 1.6, "showCursor": true } }
|
|
1755
|
+
]
|
|
1756
|
+
}
|
|
1757
|
+
},
|
|
1758
|
+
{
|
|
1759
|
+
"id": "s3",
|
|
1760
|
+
"durationInFrames": 90,
|
|
1761
|
+
"transitionToNext": { "type": "WipeTransition", "durationInFrames": 15, "props": { "direction": "diagonal", "softEdge": true } },
|
|
1762
|
+
"root": {
|
|
1763
|
+
"id": "scene-3",
|
|
1764
|
+
"type": "Scene",
|
|
1765
|
+
"props": { "background": { "type": "color", "value": "#111827" } },
|
|
1766
|
+
"children": [
|
|
1767
|
+
{ "id": "title-3", "type": "SplitText", "props": { "content": "WipeTransition", "fontSize": 96, "splitBy": "word", "stagger": 3, "animation": "scale", "position": "center" } },
|
|
1768
|
+
{ "id": "subtitle-3", "type": "TypewriterText", "props": { "content": "direction=diagonal, softEdge=true", "fontSize": 44, "position": "bottom", "speed": 1.6, "showCursor": false } }
|
|
1769
|
+
]
|
|
1770
|
+
}
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
"id": "s4",
|
|
1774
|
+
"durationInFrames": 90,
|
|
1775
|
+
"root": {
|
|
1776
|
+
"id": "scene-4",
|
|
1777
|
+
"type": "Scene",
|
|
1778
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
1779
|
+
"children": [
|
|
1780
|
+
{ "id": "title-4", "type": "SplitText", "props": { "content": "Done", "fontSize": 120, "splitBy": "letter", "stagger": 2, "animation": "rotate", "position": "center" } },
|
|
1781
|
+
{ "id": "wm-4", "type": "Watermark", "props": { "type": "text", "text": "@depths.ai", "position": "bottomRight", "opacity": 0.4, "size": 60 } }
|
|
1782
|
+
]
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
]
|
|
1786
|
+
}
|
|
1787
|
+
```
|
|
1788
|
+
|
|
1789
|
+
### Example 3: data dashboard
|
|
1790
|
+
|
|
1791
|
+
File: `examples/data-dashboard.v2.json`
|
|
1792
|
+
|
|
1793
|
+
This example uses data composites inside a `Grid`:
|
|
1794
|
+
|
|
1795
|
+
- `ProgressRing`
|
|
1796
|
+
- `BarChart`
|
|
1797
|
+
- `LineGraph`
|
|
1798
|
+
- `ProgressBar`
|
|
1799
|
+
|
|
1800
|
+
```json
|
|
1801
|
+
{
|
|
1802
|
+
"version": "2.0",
|
|
1803
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 180 },
|
|
1804
|
+
"segments": [
|
|
1805
|
+
{
|
|
1806
|
+
"id": "dashboard",
|
|
1807
|
+
"durationInFrames": 180,
|
|
1808
|
+
"root": {
|
|
1809
|
+
"id": "scene",
|
|
1810
|
+
"type": "Scene",
|
|
1811
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
1812
|
+
"children": [
|
|
1813
|
+
{
|
|
1814
|
+
"id": "grid",
|
|
1815
|
+
"type": "Grid",
|
|
1816
|
+
"props": { "columns": 2, "rows": 2, "gap": 36, "padding": 90, "align": "stretch", "justify": "stretch" },
|
|
1817
|
+
"children": [
|
|
1818
|
+
{ "id": "ring", "type": "ProgressRing", "props": { "percentage": 72, "size": 260, "color": "#22C55E", "showLabel": true } },
|
|
1819
|
+
{
|
|
1820
|
+
"id": "bars",
|
|
1821
|
+
"type": "BarChart",
|
|
1822
|
+
"props": {
|
|
1823
|
+
"orientation": "vertical",
|
|
1824
|
+
"showValues": true,
|
|
1825
|
+
"showGrid": true,
|
|
1826
|
+
"data": [
|
|
1827
|
+
{ "label": "A", "value": 32, "color": "#3B82F6" },
|
|
1828
|
+
{ "label": "B", "value": 56, "color": "#22C55E" },
|
|
1829
|
+
{ "label": "C", "value": 18, "color": "#F59E0B" },
|
|
1830
|
+
{ "label": "D", "value": 44, "color": "#EF4444" }
|
|
1831
|
+
]
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
"id": "line",
|
|
1836
|
+
"type": "LineGraph",
|
|
1837
|
+
"props": {
|
|
1838
|
+
"color": "#60A5FA",
|
|
1839
|
+
"strokeWidth": 4,
|
|
1840
|
+
"showDots": true,
|
|
1841
|
+
"fillArea": true,
|
|
1842
|
+
"animate": "draw",
|
|
1843
|
+
"data": [
|
|
1844
|
+
{ "x": 0, "y": 10 },
|
|
1845
|
+
{ "x": 1, "y": 16 },
|
|
1846
|
+
{ "x": 2, "y": 12 },
|
|
1847
|
+
{ "x": 3, "y": 22 },
|
|
1848
|
+
{ "x": 4, "y": 18 },
|
|
1849
|
+
{ "x": 5, "y": 28 }
|
|
1850
|
+
]
|
|
1851
|
+
}
|
|
1852
|
+
},
|
|
1853
|
+
{ "id": "progress", "type": "ProgressBar", "props": { "label": "Rendering", "position": "bottom", "height": 16, "color": "#A855F7", "showPercentage": true } }
|
|
1854
|
+
]
|
|
1855
|
+
},
|
|
1856
|
+
{ "id": "title", "type": "SplitText", "props": { "content": "Data composites", "fontSize": 86, "splitBy": "word", "stagger": 3, "animation": "slideUp", "position": "top" } }
|
|
1857
|
+
]
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
]
|
|
1861
|
+
}
|
|
1862
|
+
```
|
|
1863
|
+
|
|
1864
|
+
### Example 4: vertical social-style video (9:16)
|
|
1865
|
+
|
|
1866
|
+
File: `examples/social-vertical.v2.json`
|
|
1867
|
+
|
|
1868
|
+
This example uses:
|
|
1869
|
+
|
|
1870
|
+
- `InstagramStory` (story UI)
|
|
1871
|
+
- `TikTokCaption` (caption UI)
|
|
1872
|
+
- a `CircularReveal` segment overlap transition
|
|
1873
|
+
|
|
1874
|
+
```json
|
|
1875
|
+
{
|
|
1876
|
+
"version": "2.0",
|
|
1877
|
+
"video": { "id": "main", "width": 1080, "height": 1920, "fps": 30, "durationInFrames": 225 },
|
|
1878
|
+
"segments": [
|
|
1879
|
+
{
|
|
1880
|
+
"id": "ig",
|
|
1881
|
+
"durationInFrames": 120,
|
|
1882
|
+
"transitionToNext": { "type": "CircularReveal", "durationInFrames": 15, "props": { "direction": "open", "center": { "x": 0.5, "y": 0.45 } } },
|
|
1883
|
+
"root": {
|
|
1884
|
+
"id": "ig-root",
|
|
1885
|
+
"type": "Scene",
|
|
1886
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
1887
|
+
"children": [
|
|
1888
|
+
{ "id": "ig", "type": "InstagramStory", "props": { "backgroundColor": "#0B1220", "username": "depths.ai", "text": "Waves v0.2.0 ships composites + hybrid IR", "sticker": "poll" } }
|
|
1889
|
+
]
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
"id": "tt",
|
|
1894
|
+
"durationInFrames": 120,
|
|
1895
|
+
"root": {
|
|
1896
|
+
"id": "tt-root",
|
|
1897
|
+
"type": "Scene",
|
|
1898
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
1899
|
+
"children": [
|
|
1900
|
+
{ "id": "caption", "type": "TikTokCaption", "props": { "text": "Composites make LLM video authoring dramatically easier", "position": "bottom", "highlightStyle": "bounce", "fontSize": 58, "strokeWidth": 4 } },
|
|
1901
|
+
{ "id": "wm", "type": "Watermark", "props": { "type": "text", "text": "@depths.ai", "position": "topRight", "opacity": 0.35, "size": 72 } }
|
|
1902
|
+
]
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
]
|
|
1906
|
+
}
|
|
1907
|
+
```
|
|
1908
|
+
|
|
1909
|
+
### Example 5: intro -> stats -> outro (assets)
|
|
1910
|
+
|
|
1911
|
+
File: `examples/intro-stats-outro.v2.json`
|
|
1912
|
+
|
|
1913
|
+
This example uses branding + layout + data composites:
|
|
1914
|
+
|
|
1915
|
+
- `IntroScene` (requires `logoSrc` and `companyName`)
|
|
1916
|
+
- `ThirdLowerBanner`, `AnimatedCounter`, `BarChart`
|
|
1917
|
+
- `OutroScene` (requires `logoSrc`)
|
|
1918
|
+
|
|
1919
|
+
It references `/assets/logo.svg` style paths; supply those files and pass `--publicDir`.
|
|
1920
|
+
|
|
1921
|
+
```json
|
|
1922
|
+
{
|
|
1923
|
+
"version": "2.0",
|
|
1924
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 360 },
|
|
1925
|
+
"segments": [
|
|
1926
|
+
{
|
|
1927
|
+
"id": "intro",
|
|
1928
|
+
"durationInFrames": 120,
|
|
1929
|
+
"transitionToNext": { "type": "FadeTransition", "durationInFrames": 15 },
|
|
1930
|
+
"root": {
|
|
1931
|
+
"id": "intro-scene",
|
|
1932
|
+
"type": "Scene",
|
|
1933
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
1934
|
+
"children": [
|
|
1935
|
+
{ "id": "intro", "type": "IntroScene", "props": { "logoSrc": "/assets/logo.svg", "companyName": "Depths AI", "tagline": "Waves v0.2.0", "backgroundColor": "#000000", "primaryColor": "#FFFFFF" } }
|
|
1936
|
+
]
|
|
1937
|
+
}
|
|
1938
|
+
},
|
|
1939
|
+
{
|
|
1940
|
+
"id": "stats",
|
|
1941
|
+
"durationInFrames": 150,
|
|
1942
|
+
"transitionToNext": { "type": "SlideTransition", "durationInFrames": 15, "props": { "direction": "up", "distance": 140 } },
|
|
1943
|
+
"root": {
|
|
1944
|
+
"id": "stats-scene",
|
|
1945
|
+
"type": "Scene",
|
|
1946
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
1947
|
+
"children": [
|
|
1948
|
+
{ "id": "lower-third", "type": "ThirdLowerBanner", "props": { "name": "Waves", "title": "Composite components", "accentColor": "#22C55E" } },
|
|
1949
|
+
{ "id": "counter", "type": "AnimatedCounter", "props": { "from": 0, "to": 44, "suffix": " types", "fontSize": 110, "color": "#FFFFFF", "animationType": "spring" } },
|
|
1950
|
+
{ "id": "bars", "type": "BarChart", "props": { "orientation": "horizontal", "showValues": true, "showGrid": false, "data": [ { "label": "Primitives", "value": 10, "color": "#3B82F6" }, { "label": "Composites", "value": 34, "color": "#22C55E" } ] } }
|
|
1951
|
+
]
|
|
1952
|
+
}
|
|
1953
|
+
},
|
|
1954
|
+
{
|
|
1955
|
+
"id": "outro",
|
|
1956
|
+
"durationInFrames": 120,
|
|
1957
|
+
"root": {
|
|
1958
|
+
"id": "outro-scene",
|
|
1959
|
+
"type": "Scene",
|
|
1960
|
+
"props": { "background": { "type": "color", "value": "#000000" } },
|
|
1961
|
+
"children": [
|
|
1962
|
+
{
|
|
1963
|
+
"id": "outro",
|
|
1964
|
+
"type": "OutroScene",
|
|
1965
|
+
"props": {
|
|
1966
|
+
"logoSrc": "/assets/logo.svg",
|
|
1967
|
+
"message": "Thanks for watching",
|
|
1968
|
+
"backgroundColor": "#000000",
|
|
1969
|
+
"ctaButtons": [
|
|
1970
|
+
{ "text": "Star", "icon": "*" },
|
|
1971
|
+
{ "text": "Follow", "icon": "->" }
|
|
1972
|
+
],
|
|
1973
|
+
"socialHandles": [
|
|
1974
|
+
{ "platform": "twitter", "handle": "@depths_ai" },
|
|
1975
|
+
{ "platform": "youtube", "handle": "@depths-ai" }
|
|
1976
|
+
]
|
|
1977
|
+
}
|
|
1978
|
+
},
|
|
1979
|
+
{ "id": "wm", "type": "Watermark", "props": { "type": "text", "text": "@depths.ai", "position": "bottomRight", "opacity": 0.25, "size": 64 } }
|
|
1980
|
+
]
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
]
|
|
1984
|
+
}
|
|
1985
|
+
```
|
|
1986
|
+
|
|
1987
|
+
### Example 6: timeline mode (global audio)
|
|
1988
|
+
|
|
1989
|
+
File: `examples/timeline-global-audio.v2.json`
|
|
1990
|
+
|
|
1991
|
+
This example uses `timeline[]` to place a global `Audio` track as a root node spanning the entire video.
|
|
1992
|
+
|
|
1993
|
+
This is a common reason to choose timeline mode over segments mode: you want "global layers" (audio, watermarks, overlays) that span multiple scenes without duplicating them in each segment.
|
|
1994
|
+
|
|
1995
|
+
```json
|
|
1996
|
+
{
|
|
1997
|
+
"version": "2.0",
|
|
1998
|
+
"video": { "id": "main", "width": 1920, "height": 1080, "fps": 30, "durationInFrames": 150 },
|
|
1999
|
+
"timeline": [
|
|
2000
|
+
{
|
|
2001
|
+
"id": "scene",
|
|
2002
|
+
"type": "Scene",
|
|
2003
|
+
"timing": { "from": 0, "durationInFrames": 150 },
|
|
2004
|
+
"props": { "background": { "type": "color", "value": "#0B1220" } },
|
|
2005
|
+
"children": [
|
|
2006
|
+
{ "id": "title", "type": "SplitText", "props": { "content": "Timeline mode", "fontSize": 96, "splitBy": "word", "stagger": 3, "animation": "slideUp", "position": "top" } },
|
|
2007
|
+
{ "id": "subtitle", "type": "TypewriterText", "props": { "content": "Audio spans the whole video", "fontSize": 44, "position": "bottom", "speed": 1.4, "showCursor": false } }
|
|
2008
|
+
]
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
"id": "music",
|
|
2012
|
+
"type": "Audio",
|
|
2013
|
+
"timing": { "from": 0, "durationInFrames": 150 },
|
|
2014
|
+
"props": { "src": "/assets/music.wav", "volume": 0.6, "fadeIn": 12, "fadeOut": 12 }
|
|
2015
|
+
}
|
|
2016
|
+
]
|
|
2017
|
+
}
|
|
2018
|
+
```
|
|
2019
|
+
|
|
2020
|
+
## Library API (quickstart)
|
|
389
2021
|
|
|
390
2022
|
```ts
|
|
391
|
-
import {
|
|
2023
|
+
import { renderVideo } from '@depths/waves';
|
|
392
2024
|
|
|
393
|
-
|
|
394
|
-
|
|
2025
|
+
await renderVideo(
|
|
2026
|
+
{
|
|
2027
|
+
version: '2.0',
|
|
2028
|
+
video: { id: 'main', width: 1920, height: 1080, fps: 30, durationInFrames: 60 },
|
|
2029
|
+
segments: [
|
|
2030
|
+
{
|
|
2031
|
+
id: 'scene-1',
|
|
2032
|
+
durationInFrames: 60,
|
|
2033
|
+
root: {
|
|
2034
|
+
id: 'root',
|
|
2035
|
+
type: 'Scene',
|
|
2036
|
+
props: { background: { type: 'color', value: '#000000' } },
|
|
2037
|
+
children: [{ id: 't1', type: 'Text', props: { content: 'Hello' } }]
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
]
|
|
2041
|
+
},
|
|
2042
|
+
{ outputPath: './output.mp4', publicDir: './public' }
|
|
2043
|
+
);
|
|
395
2044
|
```
|
|
396
2045
|
|
|
397
|
-
|
|
2046
|
+
## Assets and paths (Windows/Linux)
|
|
398
2047
|
|
|
399
|
-
|
|
400
|
-
- The component’s metadata (description, examples, etc.)
|
|
2048
|
+
In IR JSON, asset paths must be either:
|
|
401
2049
|
|
|
402
|
-
|
|
2050
|
+
- a full URL (`https://...`), or
|
|
2051
|
+
- a `/assets/...` path (leading slash) that is resolved relative to `--publicDir` / `publicDir`.
|
|
403
2052
|
|
|
404
|
-
|
|
2053
|
+
On Windows:
|
|
405
2054
|
|
|
406
|
-
-
|
|
407
|
-
-
|
|
408
|
-
- Scene start frames must be sequential with no gaps/overlaps
|
|
409
|
-
- Asset paths are either full URLs or absolute paths like `/assets/foo.png`
|
|
2055
|
+
- Prefer forward slashes in IR (`/assets/foo.png`), not `C:\\...` paths.
|
|
2056
|
+
- If you pass Windows paths to the CLI (e.g. `--out`), quote them if they contain spaces.
|
|
410
2057
|
|
|
411
|
-
##
|
|
2058
|
+
## Rendering prerequisites
|
|
412
2059
|
|
|
413
|
-
|
|
2060
|
+
Waves uses `@remotion/renderer` (Remotion 4.x). Remotion runs headless Chromium; if a compatible browser isn't available, it may download one automatically (or you can configure browser paths via Remotion). Remotion 4 also ships its own ffmpeg binary, so you typically do not need `ffmpeg` installed on your PATH.
|
|
414
2061
|
|
|
415
|
-
|
|
2062
|
+
## Local CLI testing (before publishing)
|
|
416
2063
|
|
|
417
|
-
|
|
2064
|
+
From the `waves/` package directory:
|
|
418
2065
|
|
|
419
|
-
|
|
2066
|
+
```bash
|
|
2067
|
+
npm install
|
|
2068
|
+
npm run build
|
|
2069
|
+
node dist/cli.js --help
|
|
2070
|
+
node dist/cli.js write-ir --template basic --pretty --out examples/basic.v2.json
|
|
2071
|
+
node dist/cli.js validate --in examples/basic.v2.json
|
|
2072
|
+
node dist/cli.js render --in examples/basic.v2.json --out examples/basic.v2.mp4 --codec h264 --crf 28 --concurrency 1
|
|
2073
|
+
```
|
|
2074
|
+
|
|
2075
|
+
To test the installed experience:
|
|
2076
|
+
|
|
2077
|
+
```bash
|
|
2078
|
+
npm link
|
|
2079
|
+
waves --help
|
|
2080
|
+
```
|
|
2081
|
+
|
|
2082
|
+
## Contributing
|
|
2083
|
+
|
|
2084
|
+
This repository is intentionally "boring": most of the value lives in strict schemas, a strict registry, and a growing component catalog.
|
|
2085
|
+
|
|
2086
|
+
### Dev setup
|
|
2087
|
+
|
|
2088
|
+
From `internal_tools/waves/`:
|
|
2089
|
+
|
|
2090
|
+
```bash
|
|
2091
|
+
npm install
|
|
2092
|
+
npm test
|
|
2093
|
+
npm run typecheck
|
|
2094
|
+
npm run lint
|
|
2095
|
+
npm run build
|
|
2096
|
+
```
|
|
2097
|
+
|
|
2098
|
+
The build emits ESM into `dist/`. Most CLI tests execute the built CLI via `node dist/cli.js` (so you catch packaging issues).
|
|
2099
|
+
|
|
2100
|
+
### Repository layout (where to look)
|
|
2101
|
+
|
|
2102
|
+
- `src/cli.ts`: the CLI (`waves <command> ...`)
|
|
2103
|
+
- `src/llm/prompt.ts`: system prompt + prompt payload (schemas + catalog)
|
|
2104
|
+
- `src/ir/schema.ts`: IR v2.0 Zod schemas (authoring + full IR)
|
|
2105
|
+
- `src/ir/migrations.ts`: compiler (`compileToRenderGraph`) from authored IR -> render timeline
|
|
2106
|
+
- `src/core/registry.ts`: component registry + JSON Schema export for LLM use
|
|
2107
|
+
- `src/core/validator.ts`: schema + semantics + registry validation
|
|
2108
|
+
- `src/core/engine.ts`: "engine" that validates, compiles, bundles, renders
|
|
2109
|
+
- `src/remotion/WavesComposition.tsx`: Remotion root composition that renders the compiled IR
|
|
2110
|
+
- `src/components/primitives/*`: low-level building blocks (`Scene`, `Text`, `Box`, etc.)
|
|
2111
|
+
- `src/components/composites/*`: higher-level components built from primitives
|
|
2112
|
+
- `src/components/registry.ts`: built-in registration of all primitives + composites
|
|
2113
|
+
- `scripts/generate-readme-components.mjs`: generates the component/props tables inside this README
|
|
2114
|
+
|
|
2115
|
+
### Adding a new component (the shadcn-like flow)
|
|
2116
|
+
|
|
2117
|
+
1) Implement a React component in `src/components/primitives/` or `src/components/composites/`.
|
|
2118
|
+
2) Define:
|
|
2119
|
+
- a Zod props schema (export as `XPropsSchema`)
|
|
2120
|
+
- a metadata object (`XComponentMetadata`) describing:
|
|
2121
|
+
- `kind`: `primitive` or `composite`
|
|
2122
|
+
- `category`: one of the known categories (layout, media, text, transition, data, social, branding, etc.)
|
|
2123
|
+
- `description`: one-line description
|
|
2124
|
+
- `llmGuidance`: (optional) short "how to use" hint for agents
|
|
2125
|
+
- children contract: `acceptsChildren`, `minChildren`, `maxChildren`
|
|
2126
|
+
3) Register it in `src/components/registry.ts` via `globalRegistry.register({ type, component, propsSchema, metadata })`.
|
|
2127
|
+
4) Add tests:
|
|
2128
|
+
- registry/schema tests: `tests/unit/components-registry.test.ts` (or adjacent unit tests)
|
|
2129
|
+
- validator tests if the component introduces new timing or child-contract semantics
|
|
2130
|
+
5) Rebuild + re-generate README component docs:
|
|
420
2131
|
|
|
421
|
-
|
|
2132
|
+
```bash
|
|
2133
|
+
npm run build
|
|
2134
|
+
node scripts/generate-readme-components.mjs
|
|
2135
|
+
```
|
|
2136
|
+
|
|
2137
|
+
6) Run the full gates:
|
|
2138
|
+
|
|
2139
|
+
```bash
|
|
2140
|
+
npm test
|
|
2141
|
+
npm run typecheck
|
|
2142
|
+
npm run lint
|
|
2143
|
+
```
|
|
2144
|
+
|
|
2145
|
+
### Updating the component docs block in README
|
|
2146
|
+
|
|
2147
|
+
The "Components (primitives + composites)" section is generated from the registry so it never drifts.
|
|
2148
|
+
|
|
2149
|
+
The generator updates only the block between:
|
|
2150
|
+
|
|
2151
|
+
- `<!-- BEGIN GENERATED: COMPONENTS -->`
|
|
2152
|
+
- `<!-- END GENERATED: COMPONENTS -->`
|
|
2153
|
+
|
|
2154
|
+
Regenerate after any component/props changes:
|
|
2155
|
+
|
|
2156
|
+
```bash
|
|
2157
|
+
npm run build
|
|
2158
|
+
node scripts/generate-readme-components.mjs
|
|
2159
|
+
```
|
|
422
2160
|
|
|
423
|
-
|
|
2161
|
+
### Adding or changing CLI commands
|
|
424
2162
|
|
|
425
|
-
|
|
426
|
-
- If it’s a custom component, ensure it is registered into `globalRegistry`
|
|
2163
|
+
The CLI is implemented as a small argument parser in `src/cli.ts` (no heavy CLI framework) to keep packaging deterministic.
|
|
427
2164
|
|
|
428
|
-
|
|
2165
|
+
When adding commands:
|
|
429
2166
|
|
|
430
|
-
|
|
2167
|
+
1) update `formatHelp()`
|
|
2168
|
+
2) update `main()` command handler
|
|
2169
|
+
3) add CLI unit tests under `tests/unit/cli-*.test.ts`
|
|
2170
|
+
4) ensure the command works from the built output (`node dist/cli.js ...`)
|
|
431
2171
|
|
|
432
|
-
|
|
2172
|
+
### Notes on renders in CI/tests
|
|
433
2173
|
|
|
434
|
-
|
|
435
|
-
|
|
2174
|
+
The integration test `tests/integration/render.test.ts` is "best-effort": it tries to render a tiny video and writes to a temp folder.
|
|
2175
|
+
If Remotion rendering fails due to missing browser binaries or other environment limitations, fix the environment rather than weakening the test.
|