@amg555/frameforge-mcp 2.4.9 → 2.7.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/.claude-plugin/marketplace.json +22 -4
- package/CHANGELOG.md +174 -0
- package/CLAUDE.md +192 -9
- package/README.md +47 -6
- package/output/harness_test_1794418727139/frames/f000000.jpg +1 -0
- package/package.json +4 -3
- package/plugins/video-pipeline/.claude-plugin/plugin.json +8 -2
- package/plugins/video-pipeline/skills/.upstream-skills.json +143 -0
- package/plugins/video-pipeline/skills/html-renderer/SKILL.md +59 -15
- package/plugins/video-pipeline/skills/html-renderer/references/animation-patterns.md +609 -0
- package/plugins/video-pipeline/skills/html-renderer/references/character-animation.md +388 -0
- package/plugins/video-pipeline/skills/html-renderer/references/collage-broll.md +41 -0
- package/plugins/video-pipeline/skills/html-renderer/references/design.md +217 -0
- package/plugins/video-pipeline/skills/html-renderer/references/directing.md +165 -0
- package/plugins/video-pipeline/skills/html-renderer/references/earshot-audio.md +201 -0
- package/plugins/video-pipeline/skills/html-renderer/references/engine-api.md +480 -0
- package/plugins/video-pipeline/skills/html-renderer/references/film-styles.md +95 -0
- package/plugins/video-pipeline/skills/html-renderer/references/hyperframes-motion.md +208 -0
- package/plugins/video-pipeline/skills/html-renderer/references/motion-craft.md +1 -0
- package/plugins/video-pipeline/skills/html-renderer/references/onetake-camera.md +59 -0
- package/plugins/video-pipeline/skills/html-renderer/references/procedural-sfx.md +135 -0
- package/plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md +159 -0
- package/plugins/video-pipeline/skills/html-renderer/references/render-contract.md +1 -0
- package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +19 -2
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +15 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +21 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1293 -20
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +36 -6
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +126 -2
- package/scripts/deep-validate.js +52 -2
- package/src/audio-mixer/mixer.js +594 -63
- package/src/cli/doctor.js +36 -2
- package/src/cli/make.js +113 -9
- package/src/harness/bin/harness.mjs +127 -5
- package/src/harness/lib/capture.mjs +215 -192
- package/src/harness/lib/verify-quality.mjs +48 -7
- package/src/mcp-server/server.js +1020 -61
- package/src/skills/sync-upstream.js +290 -0
- package/src/utils/media-downloader.js +162 -0
- package/src/utils/transcribe.js +341 -0
- package/src/video-editor/editor.js +96 -11
- package/src/video-renderer/renderer.js +7 -1
- package/src/video-renderer/scene-animator.js +143 -1
- package/src/workflows/assets.js +4 -0
- package/src/workflows/ingest.js +10 -0
- package/src/workflows/render.js +2 -0
- package/src/workflows/script.js +18 -10
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
# Engine API — Full Function Reference
|
|
2
|
+
|
|
3
|
+
Source: `reel/engine.js`
|
|
4
|
+
Load with: `import { reel, layer, at, stagger, seek, hash, ... } from './reel/engine.js';`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Configuration
|
|
9
|
+
|
|
10
|
+
### `reel({ width, height, fps, duration, title })`
|
|
11
|
+
Sets global compositor dimensions and timeline length. Call **once** at the top of `edit.js`.
|
|
12
|
+
Sets `window.DURATION` and `window.FPS` — required by the render harness.
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
reel({ width: 1920, height: 1080, fps: 30, duration: 65 });
|
|
16
|
+
reel({ width: 1080, height: 1920, fps: 30, duration: 30 }); // portrait / reel
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`REEL.captionY` — override caption baseline (px from top, default centre-bottom).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Layer System
|
|
24
|
+
|
|
25
|
+
### `layer(name, { start?, end? }) → Layer`
|
|
26
|
+
Declare a named visual layer. Returns the same Layer if called twice with the same name.
|
|
27
|
+
`start` / `end` define the shot window in seconds (default: whole film).
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
layer('hero', { start: 3, end: 12 }).on((t, lt, dur) => ({
|
|
31
|
+
opacity: lt < 0.4 ? lt / 0.4 : lt > dur - 0.4 ? (dur - lt) / 0.4 : 1,
|
|
32
|
+
transform: `translateY(${(1 - spring(lt)) * 60}px)`,
|
|
33
|
+
}));
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Callback signature: `fn(t, lt, dur) → styleObject`**
|
|
37
|
+
- `t` — absolute video time (seconds)
|
|
38
|
+
- `lt` — local time within the shot (`t - start`)
|
|
39
|
+
- `dur` — shot duration (`end - start`)
|
|
40
|
+
|
|
41
|
+
**Iron rule:** Every CSS property driven by this layer must be explicitly reset when the layer is inactive. If you return `{ opacity: 1 }` at `lt=5` but the next frame is `lt=6` where you return `null`, the browser keeps `opacity: 1`. Use:
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
if (lt > dur) return { opacity: 0, display: 'none' };
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Timed Element Placement
|
|
50
|
+
|
|
51
|
+
### `at(t, element)`
|
|
52
|
+
Mount a DOM element, made visible only while `t >= start` and `t < start + element._duration`.
|
|
53
|
+
|
|
54
|
+
### `stagger(t, dt, elements[])`
|
|
55
|
+
Place elements with staggered delays. Returns `[{t, element}]` timing log.
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
stagger(2.0, 0.18, [card1, card2, card3]);
|
|
59
|
+
// card1 appears at t=2.0, card2 at t=2.18, card3 at t=2.36
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Math & Easing
|
|
65
|
+
|
|
66
|
+
### `hash(i) → float [0, 1)`
|
|
67
|
+
Deterministic sin-based hash of integer `i`. **Use instead of `Math.random()`.**
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const x = hash(particleIndex) * REEL.width;
|
|
71
|
+
const wobble = hash(i + Math.floor(t * 12)); // boil at 12fps
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `hash2(i, j) → float [0, 1)`
|
|
75
|
+
Two-dimensional hash for grid seeds: `hash(i * 1000 + j)`.
|
|
76
|
+
|
|
77
|
+
### `lerp(t, a, b, t0?, t1?) → number`
|
|
78
|
+
Linear interpolation from `a` to `b` over `[t0, t1]`, clamped.
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
lerp(lt, 0, 100, 0, 0.5) // 0→100 in first 0.5 s of shot
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `easeIn(t)`, `easeOut(t)`, `easeInOut(t)`
|
|
85
|
+
Quadratic easing. Input `[0, 1]`, output `[0, 1]`.
|
|
86
|
+
|
|
87
|
+
### `spring(t, { tension?, friction?, delay? }) → float`
|
|
88
|
+
Closed-form damped harmonic oscillator. Returns `0 → ~1` (may briefly exceed 1 when underdamped).
|
|
89
|
+
|
|
90
|
+
| Preset | tension | friction | Character |
|
|
91
|
+
|--------|---------|----------|-----------|
|
|
92
|
+
| Snappy | 280 | 28 | Fast settle, tiny overshoot |
|
|
93
|
+
| Default | 200 | 22 | Natural entrance |
|
|
94
|
+
| Bouncy | 160 | 13 | Visible overshoot |
|
|
95
|
+
| Gentle | 100 | 20 | Slow, overdamped |
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const s = spring(lt, { tension: 200, friction: 22, delay: 0.2 });
|
|
99
|
+
// opacity: s, transform: `scale(${0.88 + 0.12 * s}) translateY(${(1-s)*40}px)`
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### `measureSpring({ tension?, friction?, threshold? }) → number`
|
|
103
|
+
Calculates the analytical settling time (in seconds) for a spring configuration. Eliminates guess-and-check when timing animations.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const settleTime = measureSpring({ tension: 200, friction: 22 }); // ~0.72s
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `interpolate(val, inputRange[], outputRange[], opts?) → number`
|
|
110
|
+
Remotion-compatible multi-segment interpolation with clamp/identity extrapolation and custom easing.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
const y = interpolate(lt, [0, 0.5, 1.2], [100, 0, -20], { extrapolateRight: 'clamp' });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `interpolateColors(val, inputRange[], outputColors[], opts?) → string`
|
|
117
|
+
Remotion-compatible perceptually uniform color interpolation with gamma correction (2.2). Blends hex and rgba colors without muddy middle grays.
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const bg = interpolateColors(lt, [0, 2], ['#111827', '#4f46e5']);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `sequence(from, duration, fn) → (t) => styleObject`
|
|
124
|
+
Temporal window wrapper that scopes computation to `[from, from + duration]`. Local time is passed as first argument.
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
layer('card').on(sequence(2.0, 4.0, (lt, dur) => ({
|
|
128
|
+
opacity: lt < 0.3 ? lt / 0.3 : 1
|
|
129
|
+
})));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `noise2D(seed, x, y) → float [-1, 1]` & `noise3D(seed, x, y, z) → float [-1, 1]`
|
|
133
|
+
Deterministic continuous simplex/perlin noise matching `@remotion/noise`. Zero dependencies. Essential for handheld camera shake, organic floating, and subtle rotational wobble.
|
|
134
|
+
|
|
135
|
+
```js
|
|
136
|
+
const shakeX = noise2D('cam', t * 3, 0) * 12; // +/-12px handheld drift
|
|
137
|
+
const shakeY = noise2D('cam', 0, t * 3) * 8;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `evolvePath(progress, pathLength) → { strokeDasharray, strokeDashoffset }`
|
|
141
|
+
Path reveal utility matching `@remotion/paths`. Generates CSS stroke dash parameters to draw SVG curves smoothly.
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
const { strokeDasharray, strokeDashoffset } = evolvePath(spring(lt), 640);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `transition`
|
|
148
|
+
Preset transition style generators matching `@remotion/transitions`.
|
|
149
|
+
- `transition.fade(p)`
|
|
150
|
+
- `transition.slide(p, { direction: 'from-left' | 'from-right' | 'from-top' | 'from-bottom' })`
|
|
151
|
+
- `transition.wipe(p, { direction: 'from-left' | 'from-right' | 'from-top' | 'from-bottom' })`
|
|
152
|
+
- `transition.flip(p, { axis: 'x' | 'y' })`
|
|
153
|
+
- `transition.zoom(p, { from: 0.8, to: 1.0 })`
|
|
154
|
+
|
|
155
|
+
### `absoluteFill(extraStyles?) → object`
|
|
156
|
+
Standard layout helper matching Remotion's `<AbsoluteFill>`:
|
|
157
|
+
`{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }`
|
|
158
|
+
|
|
159
|
+
### `loopTime(t, duration) → number`
|
|
160
|
+
Loops local time within `[0, duration)` for periodic cycles, matching Remotion `<Loop>`.
|
|
161
|
+
|
|
162
|
+
### `loadGoogleFont(family, opts?) → Promise<void>`
|
|
163
|
+
Loads Google Fonts and prevents FOUT during headless rendering by integrating with the `delayRender` / `continueRender` async barrier.
|
|
164
|
+
|
|
165
|
+
### `kf(t, keys[], easing?) → number | number[]`
|
|
166
|
+
Keyframe interpolation with easing.
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
kf(lt, [[0, 0], [0.5, 100], [1.5, 80]], easeOut)
|
|
170
|
+
kf(lt, [[0, [0,0]], [1, [200,100]]]) // vector interpolation
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `seg(t, a, b) → float [0, 1]`
|
|
174
|
+
Normalise `t` into `[a, b]`: `(t - a) / (b - a)`, clamped.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const progress = seg(lt, 0.5, 1.5); // 0 before 0.5s, 1 after 1.5s
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### `pulse(t, k?) → float`
|
|
181
|
+
Exponential beat pulse: spikes at integer `t`, decays. `k` controls decay rate (default 6).
|
|
182
|
+
|
|
183
|
+
### `onTwos(t) → number`
|
|
184
|
+
Quantize time to 12 fps step — for traditional animation "on twos" look.
|
|
185
|
+
|
|
186
|
+
### `boilSeed(key, t?) → float [0, 1)`
|
|
187
|
+
Deterministic per-12fps-step seed for linework boil. Restarts stream per element name so parallel elements don't shift each other's randomness.
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const wobble = (boilSeed('outline', t) * 6) - 3; // –3 to +3 px jitter
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Shot Registry
|
|
196
|
+
|
|
197
|
+
### `shots([ [t0, fn0], [t1, fn1], ... ])`
|
|
198
|
+
Register time-ordered shots. Each `fn(t, lt, dur)` is called only while its window is active. Cleaner than individual `layer()` calls for long films.
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
shots([
|
|
202
|
+
[0, (t, lt, dur) => { /* intro */ }],
|
|
203
|
+
[5, (t, lt, dur) => { /* hook */ }],
|
|
204
|
+
[12, (t, lt, dur) => { /* reveal */ }],
|
|
205
|
+
]);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Component Builders
|
|
211
|
+
|
|
212
|
+
All components return `HTMLElement` instances. Mount with `at(t, element)` or `document.getElementById('stage').appendChild(element)`.
|
|
213
|
+
|
|
214
|
+
### `text(str, opts)`
|
|
215
|
+
Animated text block.
|
|
216
|
+
|
|
217
|
+
| Opt | Default | Notes |
|
|
218
|
+
|-----|---------|-------|
|
|
219
|
+
| `font` | `48px "Nirmala UI",...` | Full CSS font shorthand |
|
|
220
|
+
| `color` | `#ffffff` | CSS color |
|
|
221
|
+
| `x` | `'center'` | px or `'center'` |
|
|
222
|
+
| `y` | `100` | px from top |
|
|
223
|
+
| `align` | `'center'` | CSS text-align |
|
|
224
|
+
|
|
225
|
+
### `chip(str, opts)`
|
|
226
|
+
Pill-shaped label. Good for tags, captions, inline badges.
|
|
227
|
+
|
|
228
|
+
### `wallCard(lines[], opts)`
|
|
229
|
+
Frosted-glass card with title + body lines. For fact cards, definitions.
|
|
230
|
+
|
|
231
|
+
```js
|
|
232
|
+
at(5.0, wallCard(['GPS TRILATERATION', 'Signal timing from 4+ satellites pinpoints location.']));
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `imageCard(src, opts)`
|
|
236
|
+
Image with optional caption overlay.
|
|
237
|
+
|
|
238
|
+
### `browserCard(src, opts)`
|
|
239
|
+
Website screenshot rendered as a card (uses `<iframe>` in non-headless mode, static image in render).
|
|
240
|
+
|
|
241
|
+
### `terminal(lines[], opts)`
|
|
242
|
+
Animated terminal output. Lines appear one by one.
|
|
243
|
+
|
|
244
|
+
### `stamp(str, opts)`
|
|
245
|
+
Bold diagonal stamp. Good for `VERIFIED`, `SOURCE`, `CLAIM` overlays.
|
|
246
|
+
|
|
247
|
+
### `codeCard(code, lang?, opts)`
|
|
248
|
+
Syntax-highlighted code block. `lang` defaults to `'js'`.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Caption Sync
|
|
253
|
+
|
|
254
|
+
### `captions(outlinePath)`
|
|
255
|
+
Load Whisper word-level JSON (`transcribe.py` output). Auto-displays caption bar synced to audio.
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
captions('audio/outline.txt');
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### `cuePhrase(phrase, offset?) → number`
|
|
262
|
+
Returns the video timestamp (seconds) when `phrase` starts being spoken.
|
|
263
|
+
**Throws a hard build error if the phrase is not found** — re-recording VO can never silently desync the film.
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
const tReveal = cuePhrase('and that is how GPS works');
|
|
267
|
+
layer('reveal').on(t => ({
|
|
268
|
+
opacity: t < tReveal ? 0 : spring(t - tReveal),
|
|
269
|
+
}));
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### `cue(sfxName, t, vol?)`
|
|
273
|
+
Trigger a sound effect at time `t`. Name resolves to `audio/sfx/<name>.mp3`.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Face Layout
|
|
278
|
+
|
|
279
|
+
### `faceAt(layout, t)`
|
|
280
|
+
Position a talking-head face layer. Layouts: `'FULL'`, `'SPLIT'`, `'PIP'`.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Raw Frame Hook
|
|
285
|
+
|
|
286
|
+
### `on(fn)`
|
|
287
|
+
Register a raw per-frame compute function outside the layer system. For custom canvas overlays or Three.js scenes.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Declarative Clips (HyperFrames)
|
|
292
|
+
|
|
293
|
+
HTML elements with `.clip[data-start]` automatically participate in the timeline without requiring manual `layer()` callbacks.
|
|
294
|
+
|
|
295
|
+
```html
|
|
296
|
+
<div class="clip" data-start="3.0" data-duration="4.5">
|
|
297
|
+
<div class="hero-title">Automated Scene</div>
|
|
298
|
+
</div>
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Attributes and CSS variables set automatically during `seek(t)`:
|
|
302
|
+
- `style.display`: `''` when active (`t >= data-start && t < data-start + data-duration`), else `'none'`.
|
|
303
|
+
- `--lt`: Local time in seconds formatted as `${lt}s` (e.g. `1.250s`).
|
|
304
|
+
- `--progress`: Normalized progression $0.0000 \to 1.0000$.
|
|
305
|
+
- `data-lt`: Numeric local time attribute.
|
|
306
|
+
- `data-progress`: Numeric progress attribute.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Camera Choreography (OneTake)
|
|
311
|
+
|
|
312
|
+
### `cameraPath(waypoints[], opts?)`
|
|
313
|
+
Defines continuous, seamless camera pans, zooms, and rotations across the composition without hard cuts.
|
|
314
|
+
|
|
315
|
+
```js
|
|
316
|
+
cameraPath([
|
|
317
|
+
[0.0, { x: 0, y: 0, zoom: 1.0, rotate: 0 }],
|
|
318
|
+
[3.5, { x: 280, y: -120, zoom: 1.35, rotate: -2 }],
|
|
319
|
+
[7.0, { x: -150, y: 80, zoom: 1.1, rotate: 1 }],
|
|
320
|
+
], {
|
|
321
|
+
target: '#stage', // DOM query selector or element to transform
|
|
322
|
+
easing: easeInOut, // Easing function for waypoint interpolation
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### `cameraShake(t, { intensity?, speed?, seed? }) → { x, y, rotate }`
|
|
327
|
+
Generates smooth, organic handheld camera vibration using 2D continuous noise.
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
const shake = cameraShake(t, { intensity: 8, speed: 3.0 });
|
|
331
|
+
stage.style.transform = `translate(${shake.x}px, ${shake.y}px) rotate(${shake.rotate}deg)`;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Frontier Visual Primitives
|
|
337
|
+
|
|
338
|
+
### `mascot(emotion?, opts?) → HTMLElement`
|
|
339
|
+
Generates a procedural SVG vector mascot character with 12 distinct emotions, 12fps hand-drawn line boil, eye blink cycles, and talking mouth flutter (derived from `ClaudeAnimationBase`).
|
|
340
|
+
|
|
341
|
+
- **Emotions**: `'neutral'`, `'happy'`, `'excited'`, `'thinking'`, `'confused'`, `'skeptical'`, `'surprised'`, `'alarmed'`, `'celebrating'`, `'proud'`, `'curious'`, `'sad'`.
|
|
342
|
+
- **Options**: `{ size: 320, color: '#f97316', speaking: false, x: 'center', y: 'center', duration: Infinity }`.
|
|
343
|
+
- **Dynamic controls**: `el.setEmotion(name)`, `el.setSpeaking(bool)`.
|
|
344
|
+
|
|
345
|
+
```js
|
|
346
|
+
const hero = mascot('excited', { size: 380, x: 'center', y: 'bottom' });
|
|
347
|
+
at(2.0, hero);
|
|
348
|
+
hero.setSpeaking(true);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### `collageCard(content, opts?) → HTMLElement`
|
|
352
|
+
Generates editorial halftone paper-collage b-roll cards with washi tape strips, 2.5D perspective tilt, and drop shadow (derived from `gbro-collage-broll`).
|
|
353
|
+
|
|
354
|
+
- **Options**: `{ width: 720, tiltX: 2, tiltY: -3, bg: '#fdfbf7', tapeColor: 'rgba(254, 240, 138, 0.88)', x: 'center', y: 'center', duration: Infinity }`.
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
const card = collageCard('<h3>KEY TAKEAWAYS</h3><p>Halftone textured paper with washi tape.</p>', {
|
|
358
|
+
width: 680,
|
|
359
|
+
tiltX: 3,
|
|
360
|
+
tiltY: -4,
|
|
361
|
+
});
|
|
362
|
+
at(4.0, card);
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Audio & Editor Upgrades
|
|
368
|
+
|
|
369
|
+
### Word-Level Karaoke Captions
|
|
370
|
+
`captions('audio/outline.json', { highlightColor: '#facc15' })` auto-formats transcript segments into word-level tokens. Spoken words receive a $1.14\times$ pop scale, active glow shadow, and high-contrast color highlight.
|
|
371
|
+
|
|
372
|
+
### Web Audio Procedural Sound Effects (Earshot)
|
|
373
|
+
During interactive development and browser preview (`!window.__playwright`), `cue('whoosh' | 'impact' | 'pop' | 'click' | 'riser', t)` automatically synthesizes procedural SFX using the Web Audio API without needing external asset files.
|
|
374
|
+
|
|
375
|
+
### J-Cut / L-Cut Audio Splits (Lemo-Opuscar)
|
|
376
|
+
In `VideoEditor.concatClipsWithTransitions`, pass `splitCut: 'j-cut'` (audio of incoming clip starts before video transition with S-curve crossfade) or `splitCut: 'l-cut'` (audio of preceding clip lingers over incoming video).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Beat-Grid (HyperFrames)
|
|
381
|
+
|
|
382
|
+
### `beatAt(index, bpm, fps) → number`
|
|
383
|
+
Returns the exact **frame number** for beat index `index`. Rounds each index independently — never accumulate.
|
|
384
|
+
|
|
385
|
+
```js
|
|
386
|
+
const fps = REEL.fps; // 30
|
|
387
|
+
const beat0 = beatAt(0, 120, fps); // frame 0
|
|
388
|
+
const beat1 = beatAt(1, 120, fps); // frame 15 (30 * 60 / 120)
|
|
389
|
+
const beat4 = beatAt(4, 120, fps); // frame 60
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### `beatTime(index, bpm) → number`
|
|
393
|
+
Returns beat `index` as a timestamp in **seconds**. Equivalent to `beatAt(index, bpm, fps) / fps` but without the fps parameter.
|
|
394
|
+
|
|
395
|
+
### `snapToBeat(t, bpm) → number`
|
|
396
|
+
Snaps a timestamp to the nearest beat boundary. Use for aligning SFX cues and cut points to the music grid.
|
|
397
|
+
|
|
398
|
+
```js
|
|
399
|
+
cue('wood', snapToBeat(3.1, 128) - 0.04); // 40ms lead on nearest beat
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## OM — OneTake Motion Library
|
|
405
|
+
|
|
406
|
+
All moves: **pure CSS functions `(t, opts) → { transform?, opacity?, clipPath? }`**.
|
|
407
|
+
Feed `t` via `seg(currentTime, start, end)` for normalised 0→1 progress within a time window.
|
|
408
|
+
|
|
409
|
+
```js
|
|
410
|
+
import { OM, seg, cue, ROOM_SFX } from './reel/engine.js';
|
|
411
|
+
|
|
412
|
+
// Standard entrance with carry:
|
|
413
|
+
layer('title').on((t) => {
|
|
414
|
+
const p = seg(t, 1.0, 2.2);
|
|
415
|
+
cue(ROOM_SFX.wood.desc && 'impact', 1.0 - 0.04); // 40ms lead
|
|
416
|
+
return OM.spring(p, { scale0: 0.6, scale1: 1.0 });
|
|
417
|
+
});
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
See `references/animation-patterns.md` section 5 for the full move catalogue with all options.
|
|
421
|
+
|
|
422
|
+
**Key moves:**
|
|
423
|
+
|
|
424
|
+
| Move | Category | Returns |
|
|
425
|
+
|------|----------|---------|
|
|
426
|
+
| `OM.spring(t, opts)` | Entrance | `transform: scale(…)` |
|
|
427
|
+
| `OM.rise(t, opts)` | Entrance | `transform: translateY(…)` |
|
|
428
|
+
| `OM.iris(t, opts)` | Entrance/carry | `clipPath: circle(…)` |
|
|
429
|
+
| `OM.morphRect(t, opts)` | Entrance/carry | `clipPath: inset(…)` |
|
|
430
|
+
| `OM.silk(t, opts)` | Entrance | `clipPath: inset(…)` edge-pull |
|
|
431
|
+
| `OM.zoomThrough(t, opts)` | Carry | `transform: scale(…)` portal |
|
|
432
|
+
| `OM.camera(t, opts)` | Camera | `transform: translate+scale` |
|
|
433
|
+
| `OM.handheld(t, opts)` | Camera | `transform: translate+rotate` |
|
|
434
|
+
| `OM.popOut(t, opts)` | Exit | `transform: scale(…)` |
|
|
435
|
+
| `OM.slideExit(t, opts)` | Exit | `transform: translateX/Y` |
|
|
436
|
+
| `OM.breathe(t, opts)` | Ambient | `transform: scale(…)` |
|
|
437
|
+
| `OM.carryScore(t)` | Oracle | number ≥ 0.5 = PASS |
|
|
438
|
+
|
|
439
|
+
### `ROOM_SFX`
|
|
440
|
+
OneTake 5-material SFX palette. Use keys as `sfxName` in `cue()`.
|
|
441
|
+
|
|
442
|
+
| Key | Material | Use for |
|
|
443
|
+
|-----|----------|---------|
|
|
444
|
+
| `air` | Air movement | Camera translation, fast wipe |
|
|
445
|
+
| `wood` | Wooden impact | Title punch, beat drop |
|
|
446
|
+
| `glass` | Glass chime | Badge reveal, icon appearance |
|
|
447
|
+
| `sub` | Sub rumble | Cinematic reveal, depth |
|
|
448
|
+
| `bubble` | Bubble pop | Light UI touch, counter tick |
|
|
449
|
+
|
|
450
|
+
All at −8 dBFS in the SFX bus. Apply 40ms perceptual lead (`cue(name, t - 0.04)`).
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## `event(type, t, meta)`
|
|
455
|
+
Declares a composition event that `render.mjs` reads via `window.__events()` after page load.
|
|
456
|
+
Render produces `out/sfx-events.json` — consumed by `mix_audio_pro` when building the SFX track.
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
event('sfx', 1.0, { sfxName: 'impact', material: 'wood', vol: 0.8 });
|
|
460
|
+
event('transition', 3.5, { label: 'iris-open' });
|
|
461
|
+
event('beat', 0.0, { bpm: 128 });
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Types:** `'sfx'` · `'transition'` · `'beat'` · `'camera'`
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Global Aliases
|
|
469
|
+
|
|
470
|
+
`engine.js` sets these on `window` for the render harness:
|
|
471
|
+
|
|
472
|
+
| Global | Points to |
|
|
473
|
+
|--------|-----------|
|
|
474
|
+
| `window.seek(t)` | `seek(t)` |
|
|
475
|
+
| `window.__seek(t)` | `seek(t)` |
|
|
476
|
+
| `window.renderFrame(t)` | `seek(t)` |
|
|
477
|
+
| `window.DURATION` | `REEL.duration` |
|
|
478
|
+
| `window.FPS` | `REEL.fps` |
|
|
479
|
+
| `window.__events()` | Returns `_events[]` (all `event()` calls) |
|
|
480
|
+
| `window.__cues` | Array of `{ sfxName, t, vol }` from `cue()` |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Film Styles Catalog — 12 Production Video Aesthetics
|
|
2
|
+
|
|
3
|
+
Derived from `lemomo-ai/lemo-opuscar` and calibrated for FrameForge's code-driven rendering and FFmpeg post-processing.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Cinematic 2.39:1 (Anamorphic)
|
|
8
|
+
- **Aspect Ratio**: 1920×804 letterboxed or 2.39:1 wide.
|
|
9
|
+
- **Palette**: Deep teal (`#0A192F`), rich copper/gold (`#D4AF37`), slate shadow (`#0D1117`).
|
|
10
|
+
- **Motion**: Slow continuous push-in (`zoom_in`, $p = 1.0 + t \times 0.04$), organic Lissajous camera drift.
|
|
11
|
+
- **Lighting**: Subtle horizontal anamorphic lens flare (`linear-gradient(90deg, transparent 45%, rgba(0,210,255,0.4) 50%, transparent 55%)`).
|
|
12
|
+
- **FFmpeg Filter**: `curves=m='0/0 0.25/0.2 0.75/0.8 1/1':r='0/0 0.5/0.53 1/1':b='0/0.03 0.5/0.48 1/0.97',eq=saturation=1.12:contrast=1.05`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 2. Product Launch (OneTake Smooth)
|
|
17
|
+
- **Concept**: Fluid, single-shot 3D hero showcase (Apple / Teenage Engineering style).
|
|
18
|
+
- **Palette**: Pure dark obsidian (`#0F0F11`), titanium silver (`#E1E4E8`), neon blue accent (`#0070F3`).
|
|
19
|
+
- **Motion**: `onetake` continuous orbit. No cuts. The stage camera glides around the hero subject using smooth spring interpolation ($k=180, d=16$).
|
|
20
|
+
- **Lighting**: Studio rim lighting with specular glass reflections and ambient occlusion.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 3. Editorial Paper Collage & Halftone (`gbro-collage-broll`)
|
|
25
|
+
- **Concept**: Physical magazine clippings, halftone screen dots, torn paper borders, 2.5D card tilt.
|
|
26
|
+
- **Palette**: Newsprint beige (`#F4EEDD`), printer black (`#1A1A1A`), risograph red (`#FF3E3E`), postal blue (`#1D4ED8`).
|
|
27
|
+
- **Motion**: Stop-motion 12fps step-stagger entrance, rotational drop with 3-property spring.
|
|
28
|
+
- **Border**: SVG `feTurbulence` torn edge filter.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 4. Cyberpunk Neon
|
|
33
|
+
- **Concept**: High-voltage dystopian tech, terminal data HUDs, glowing reticles.
|
|
34
|
+
- **Palette**: Terminal black (`#05050A`), laser cyan (`#00F0FF`), electric magenta (`#FF0055`), hazard amber (`#FFE600`).
|
|
35
|
+
- **Motion**: Fast speed ramps (1.25x), chromatic aberration on transition, micro-glitch displacement.
|
|
36
|
+
- **FFmpeg Filter**: `curves=r='0/0 0.5/0.45 1/1':b='0/0.08 0.5/0.58 1/1',eq=saturation=1.3:contrast=1.1`.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 5. Vintage 16mm Film
|
|
41
|
+
- **Concept**: Archival documentary, analog imperfections, warm organic glow.
|
|
42
|
+
- **Palette**: Warm sepia (`#3D2F1D`), aged ivory (`#F5EBE1`), faded olive (`#4A503D`).
|
|
43
|
+
- **Texture**: Seeded 12fps film grain, gentle gate weave ($y \pm 1.5\text{px}$), vignetted rounded corners.
|
|
44
|
+
- **FFmpeg Filter**: `curves=vintage,eq=contrast=0.95:saturation=0.85`.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 6. Minimal Swiss Monochrome
|
|
49
|
+
- **Concept**: Grid-aligned brutalist architecture, massive typography, bold numbers.
|
|
50
|
+
- **Palette**: Stark white (`#FFFFFF`), pure pitch (`#000000`), international safety orange (`#FF5500`).
|
|
51
|
+
- **Typography**: Heavy grotesque sans-serif (`Archivo`, `Cabinet Grotesk`, `Inter Display`).
|
|
52
|
+
- **Motion**: Rigid geometric slides along orthogonal 12-column grid.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 7. Anime Action & Speedlines
|
|
57
|
+
- **Concept**: Shonen battle energy, radiant impact lines, frame freeze punches.
|
|
58
|
+
- **Palette**: Midnight navy (`#0B0E14`), flash yellow (`#FFE500`), crimson fire (`#FF1E27`).
|
|
59
|
+
- **Motion**: 1-frame impact white flash on narrative beat drops, radiating radial vector lines, extreme punch-in (1.20x).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 8. Retro VHS Cassette
|
|
64
|
+
- **Concept**: 1980s magnetic tape playback, tracking errors, scanlines.
|
|
65
|
+
- **Palette**: CRT green tint (`#102A12`), phosphorescent white (`#E6FFE6`).
|
|
66
|
+
- **Overlay**: Blinking REC symbol, monospace timestamp in corner (`SP 00:04:12`), subtle horizontal sync tear.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 9. Sci-Fi Vector HUD
|
|
71
|
+
- **Concept**: Telemetry telemetry data, circular reticles, radar sweeps, coordinate displays.
|
|
72
|
+
- **Palette**: Dark space blue (`#020813`), tactical emerald (`#00FF88`), radar amber (`#FFB300`).
|
|
73
|
+
- **Motion**: Continuously rotating bearing dials ($\omega = 0.5\text{rad/s}$), pulsing wave graphs.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 10. Lo-Fi Chill / Bedroom Beats
|
|
78
|
+
- **Concept**: Calm pastel aesthetic, ambient floating particles, soft shadows.
|
|
79
|
+
- **Palette**: Lavender dusk (`#9D8DF1`), dusty rose (`#B88698`), cream peach (`#FFEEDB`).
|
|
80
|
+
- **Motion**: Ultra-smooth easing (`easeInOutQuad`), floating dust motes with sine drift.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 11. Broadcast News / Financial Explainer
|
|
85
|
+
- **Concept**: CNBC / Bloomberg ticker, multi-panel layout, telemetry graphs.
|
|
86
|
+
- **Palette**: Financial blue (`#0A2540`), green profit (`#00D924`), alert red (`#E60000`).
|
|
87
|
+
- **Components**: Scrolling live ticker at bottom, animated line chart drawing left to right.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 12. 3D Isometric Blueprint
|
|
92
|
+
- **Concept**: Technical engineering breakdown, wireframe orthographic perspective.
|
|
93
|
+
- **Palette**: Blueprint cobalt (`#0D3B66`), drafting cyan (`#64DFDF`), technical white (`#F8F9FA`).
|
|
94
|
+
- **Projection**: `rotateX(60deg) rotateZ(-45deg)` CSS 3D stage with layered extruded cards.
|
|
95
|
+
|