@amg555/frameforge-mcp 2.3.2 → 2.3.3
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/.mcp.json +2 -4
- package/CHANGELOG.md +1 -1
- package/CLAUDE.md +104 -26
- package/README.md +16 -12
- package/SECURITY.md +1 -3
- package/package.json +7 -3
- package/plugins/video-pipeline/.claude-plugin/plugin.json +19 -0
- package/plugins/video-pipeline/skills/html-renderer/SKILL.md +292 -0
- package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +308 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +257 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/STORYBOARD.md +77 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/index.html +80 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +43 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +56 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +42 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.css +140 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +870 -0
- package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +138 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +167 -0
- package/plugins/video-pipeline/skills/video-pipeline/references/providers.md +163 -0
- package/scripts/db-migrate.js +112 -0
- package/scripts/deep-validate.js +326 -0
- package/scripts/generate-initial-assets.js +342 -0
- package/scripts/html-renderer/new-project.mjs +408 -0
- package/scripts/html-renderer/transcribe.py +294 -0
- package/src/asset-library/README.md +2 -2
- package/src/asset-library/asset-library.js +1 -1
- package/src/cli/make.js +52 -71
- package/src/cli/setup-keys.js +15 -17
- package/src/fact-checker/review-gate.js +5 -1
- package/src/harness/bin/harness.mjs +324 -0
- package/src/harness/lib/browser.mjs +137 -0
- package/src/harness/lib/capture.mjs +161 -0
- package/src/harness/lib/encode.mjs +89 -0
- package/src/harness/lib/jobs.mjs +135 -0
- package/src/harness/lib/pixels.mjs +137 -0
- package/src/harness/lib/sheets.mjs +164 -0
- package/src/harness/lib/verify-purity.mjs +119 -0
- package/src/harness/test/fixtures/broken-scene.html +27 -0
- package/src/harness/test/fixtures/reference-scene.html +23 -0
- package/src/harness/test/fixtures/sample-script.json +18 -0
- package/src/mcp-server/server.js +285 -144
- package/src/pipeline/telemetry.js +1 -0
- package/src/utils/log-redaction.js +5 -3
- package/src/video-renderer/renderer.js +12 -4
- package/src/worker/server-render-free.js +2 -3
- package/src/workflows/assets.js +20 -43
- package/src/workflows/ingest.js +5 -5
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# ANIMATION_GUIDE.md — Agent Context for HTML Video Projects
|
|
2
|
+
|
|
3
|
+
> This file is loaded as context by the model before it writes any scene code.
|
|
4
|
+
> It encodes the contract, the four storyboard gates, and the six determinism rules
|
|
5
|
+
> that make headless-Chrome rendering reproducible and parallel.
|
|
6
|
+
>
|
|
7
|
+
> Source: ClaudeAnimationBase ANIMATION_GUIDE.md + awesomeclaude.ai corpus study (703 prompts).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## The Contract
|
|
12
|
+
|
|
13
|
+
A video is a set of scene files where **every pixel is a pure function of time `t`**.
|
|
14
|
+
|
|
15
|
+
The renderer owns the clock. Scenes never call `Date.now()`, `performance.now()`,
|
|
16
|
+
`setTimeout`, `setInterval`, `Math.random()`, or any module-level mutable counter.
|
|
17
|
+
The renderer injects a virtual clock shim before the page loads, advances it one frame
|
|
18
|
+
at a time, and screenshots the result. Frame 400 can be captured hours after frame 399
|
|
19
|
+
and the scene must not care.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Step 0 — Beat Sheet First
|
|
24
|
+
|
|
25
|
+
**Never write scene code before the storyboard passes all four gates.**
|
|
26
|
+
|
|
27
|
+
The workflow is always:
|
|
28
|
+
|
|
29
|
+
1. Write a beat sheet (title, shots, events, reads, transitions).
|
|
30
|
+
2. Reply with the beat sheet and four keyframe descriptions **only**. Stop.
|
|
31
|
+
3. Wait for approval.
|
|
32
|
+
4. Only after approval: write the scene files.
|
|
33
|
+
|
|
34
|
+
Fixing a line in a beat sheet costs one message. Fixing a finished scene costs a rewrite.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## The Four Storyboard Gates
|
|
39
|
+
|
|
40
|
+
Run `node scripts/storyboard-validator.mjs STORYBOARD.md` to enforce these automatically.
|
|
41
|
+
|
|
42
|
+
### Gate 1 — Every shot has an EVENT
|
|
43
|
+
|
|
44
|
+
An event is something that **physically happens** in the shot. It is not a description
|
|
45
|
+
of what the viewer sees, and it is not a mood adjective.
|
|
46
|
+
|
|
47
|
+
| ❌ FAILS | ✅ PASSES |
|
|
48
|
+
|---|---|
|
|
49
|
+
| "Clawd stands in a meadow." | "Clawd turns to face the viewer and extends one hand." |
|
|
50
|
+
| "A beautiful space scene." | "A comet crosses the frame left-to-right over 4 seconds." |
|
|
51
|
+
| "We see the city at night." | "The city's lights flicker off one by one from the edges in." |
|
|
52
|
+
|
|
53
|
+
### Gate 2 — Reads must not overlap
|
|
54
|
+
|
|
55
|
+
Each shot has a list of **reads** — the discrete things a viewer must understand,
|
|
56
|
+
with non-overlapping start and end timestamps.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
# BAD — 0.0–1.5 and 1.0–2.5 overlap from 1.0–1.5
|
|
60
|
+
reads:
|
|
61
|
+
- 0.0–1.5: Viewer recognises the character.
|
|
62
|
+
- 1.0–2.5: Viewer understands she is running away.
|
|
63
|
+
|
|
64
|
+
# GOOD
|
|
65
|
+
reads:
|
|
66
|
+
- 0.0–2.0: Viewer recognises the character.
|
|
67
|
+
- 2.0–4.0: Viewer understands she is running away.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Gate 3 — Every seam has a named transition
|
|
71
|
+
|
|
72
|
+
This includes the film open (transition_in on Shot 1) and film close
|
|
73
|
+
(transition_out on the last shot). A storyboard that starts or ends
|
|
74
|
+
without a declared transition fails.
|
|
75
|
+
|
|
76
|
+
Recognised transition names:
|
|
77
|
+
`cut` `fade-from-black` `fade-to-black` `dissolve` `wipe-left` `wipe-right`
|
|
78
|
+
`iris-in` `iris-out` `cross-dissolve` `push-left` `push-right` `flash` `dip-to-white`
|
|
79
|
+
|
|
80
|
+
### Gate 4 — No text on screen
|
|
81
|
+
|
|
82
|
+
"Lost" must be acted, not captioned. "Heartbroken" must be shown, not written.
|
|
83
|
+
|
|
84
|
+
The model **may not** write `text()`, `wallCard()`, `stamp()`, or `codeCard()` calls
|
|
85
|
+
that convey plot-critical information (character names, place names, dates, emotions,
|
|
86
|
+
narrative exposition). These helpers exist for UI/diagram/explainer content only,
|
|
87
|
+
not for storytelling that belongs in the direction.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Six Determinism Rules
|
|
92
|
+
|
|
93
|
+
These rules are what make the render reproducible, parallel, and resumable.
|
|
94
|
+
|
|
95
|
+
### Rule 1 — Read time, never count frames
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
// ❌ BAD — frame counter mutates across calls
|
|
99
|
+
let frame = 0
|
|
100
|
+
layer('x').on(() => { frame++; return { opacity: frame / 30 } })
|
|
101
|
+
|
|
102
|
+
// ✅ GOOD — pure function of t
|
|
103
|
+
layer('x').on(t => ({ opacity: Math.min(1, t / 1.0) }))
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Rule 2 — Seed everything with `hash(i)`
|
|
107
|
+
|
|
108
|
+
`hash(i)` in `reel/engine.js` is a deterministic integer hash of its index.
|
|
109
|
+
Use it wherever you need a stable pseudo-random value (star positions, brush seeds,
|
|
110
|
+
particle offsets). Never use `Math.random()` inside a scene file.
|
|
111
|
+
|
|
112
|
+
```js
|
|
113
|
+
import { hash } from './reel/engine.js'
|
|
114
|
+
|
|
115
|
+
// Star field — same every render
|
|
116
|
+
for (let i = 0; i < 200; i++) {
|
|
117
|
+
const x = (hash(i) % 1920)
|
|
118
|
+
const y = (hash(i + 1) % 1080)
|
|
119
|
+
// ...
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Boil wobble exception:** hand-drawn wobble is deliberately re-seeded 12 times
|
|
124
|
+
per second so it looks chaotic while remaining reproducible:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
const boilSeed = Math.floor(t * 12) // same bucket for every frame in that 1/12s window
|
|
128
|
+
const wobble = (hash(boilSeed) % 7) - 3 // –3 to +3 px
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Rule 3 — No integrating physics
|
|
132
|
+
|
|
133
|
+
Never integrate velocity per tick. Use closed-form motion or precompute the
|
|
134
|
+
simulation into an array before the layer is registered.
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
// ❌ BAD — position depends on previous frame
|
|
138
|
+
let vx = 0, x = 0
|
|
139
|
+
layer('ball').on(() => { vx += 0.1; x += vx; return { left: x } })
|
|
140
|
+
|
|
141
|
+
// ✅ GOOD — closed-form quadratic
|
|
142
|
+
layer('ball').on(t => ({ left: 0.5 * 120 * t * t + 'px' }))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Rule 4 — Await `document.fonts.ready` before frame 0
|
|
146
|
+
|
|
147
|
+
The renderer calls `await page.evaluate(() => document.fonts.ready)` before
|
|
148
|
+
advancing the clock. Scene code must not assume fonts are available before that
|
|
149
|
+
await resolves — if a font load fails, every text frame renders in the system
|
|
150
|
+
fallback and the entire video looks wrong.
|
|
151
|
+
|
|
152
|
+
### Rule 5 — ffmpeg owns audio
|
|
153
|
+
|
|
154
|
+
Never capture audio from the browser. Audio is a separate pass:
|
|
155
|
+
|
|
156
|
+
1. TTS generates audio → word-level timestamps from `scripts/transcribe.py`
|
|
157
|
+
2. Scene resolves animation anchors from those timestamps via `cuePhrase()`
|
|
158
|
+
3. `node scripts/render.mjs full` renders video frames (silent)
|
|
159
|
+
4. `uv run scripts/mix_audio.py` builds the final audio track
|
|
160
|
+
5. `node scripts/render.mjs mux` merges with `-c:v copy` (video stream untouched)
|
|
161
|
+
|
|
162
|
+
`cuePhrase(phrase)` **throws a fatal build error** if the phrase cannot be found
|
|
163
|
+
in the loaded captions track. If a phrase stops being spoken, the build fails and
|
|
164
|
+
names the missing words — re-recording voice-over can never silently desync the film.
|
|
165
|
+
|
|
166
|
+
### Rule 6 — Tag BT.709 and use `-t` not `-shortest`
|
|
167
|
+
|
|
168
|
+
The renderer's ffmpeg encode always includes:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
-color_primaries bt709 -color_trc bt709 -colorspace bt709
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Untagged HD video gets decoded with the wrong matrix on some players and colours shift.
|
|
175
|
+
|
|
176
|
+
Always use `-t <duration>` for the output duration, not `-shortest`. `-shortest`
|
|
177
|
+
hangs if the input has a subtitle or data stream.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Motion Vocabulary
|
|
182
|
+
|
|
183
|
+
Write these explicitly in the brief. The model will follow them.
|
|
184
|
+
|
|
185
|
+
**Allowed:** easeIn, easeOut, easeInOut, spring(t), lerp(t, a, b), fade, slide, scale, rotate.
|
|
186
|
+
|
|
187
|
+
**Banned (AI slop tells):**
|
|
188
|
+
- Particle bursts that emit from a single point
|
|
189
|
+
- Lens flares
|
|
190
|
+
- Neon glow on everything
|
|
191
|
+
- RGB chromatic-aberration split used decoratively
|
|
192
|
+
- Bouncy easing on text (`spring` on entrance is OK; double-bounce is not)
|
|
193
|
+
- Perspective / `rotateY` in a flat/painted 2D style (breaks layout math and look)
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Review Surface
|
|
198
|
+
|
|
199
|
+
Before `node scripts/render.mjs full`, always generate stills:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Shape of the film — first/middle/last of each shot
|
|
203
|
+
node scripts/render.mjs sheet
|
|
204
|
+
|
|
205
|
+
# Every frame of one motion or transition
|
|
206
|
+
node scripts/render.mjs strip 2.1 2.6
|
|
207
|
+
|
|
208
|
+
# Story-carrying face or detail at full resolution
|
|
209
|
+
node scripts/render.mjs storyboard
|
|
210
|
+
|
|
211
|
+
# Mobile crop check
|
|
212
|
+
node scripts/render.mjs crop 9:16 3.0
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Budget: **at least one sheet per shot, a strip per key motion and transition,
|
|
216
|
+
a crop per story-carrying face.**
|
|
217
|
+
|
|
218
|
+
Feed the PNGs back to the model. Name what failed. Then re-render the affected range.
|
|
219
|
+
Re-rendering frame 400 costs nothing. Unguided revision costs everything.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Pre-render checklist (run before `render full`)
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
node scripts/look.mjs # fonts, clipped text, NaN transforms, missing images
|
|
227
|
+
node scripts/storyboard-validator.mjs STORYBOARD.md # all four gates
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Both must exit 0.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Subagent rules (long films)
|
|
235
|
+
|
|
236
|
+
- One orchestrator, N chapter subagents.
|
|
237
|
+
- **Chapter subagents may not edit shared helpers** (`reel/engine.js`, `reel/engine.css`,
|
|
238
|
+
`scripts/render.mjs`, `scripts/look.mjs`).
|
|
239
|
+
- Each subagent loads its rules from `pipeline/briefings/<chapter>.md` on disk, not
|
|
240
|
+
from the parent context. This keeps context small and rules isolated.
|
|
241
|
+
- Verification runs with subagents: 2 subagents at a time, each scanning 10 frames,
|
|
242
|
+
every 5 frames of the rendered sequence.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Cost reference (single data points, not benchmarks)
|
|
247
|
+
|
|
248
|
+
| Project | Cost |
|
|
249
|
+
|---|---|
|
|
250
|
+
| 15s launch spot from a one-liner | ~$2 API |
|
|
251
|
+
| Full lyric music video (high effort) | ~$200 |
|
|
252
|
+
| 3-min Remotion film (~7,400 lines) | 7% of weekly quota |
|
|
253
|
+
| Model tokens alone (~90K in / 15K out) | ~$0.66 at Opus 5.5 list price |
|
|
254
|
+
|
|
255
|
+
Render is **free and local**. The expensive part is model iteration, which is why
|
|
256
|
+
the storyboard gate and the still-review loop exist — they eliminate the most
|
|
257
|
+
expensive kind of work: finishing a scene before discovering it was wrong.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# STORYBOARD.md — Shot List Template
|
|
2
|
+
|
|
3
|
+
> Fill this out and validate it with `node scripts/storyboard-validator.mjs STORYBOARD.md`
|
|
4
|
+
> before writing any scene code. All four gates must pass.
|
|
5
|
+
>
|
|
6
|
+
> Gate 1 — Every shot has an EVENT (what physically happens), not a description.
|
|
7
|
+
> Gate 2 — Reads within each shot have non-overlapping timestamps.
|
|
8
|
+
> Gate 3 — Every seam has a named transition, including the film open and close.
|
|
9
|
+
> Gate 4 — No on-screen text. Emotion and story must be acted, not captioned.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Film
|
|
14
|
+
|
|
15
|
+
- title: My Film
|
|
16
|
+
- duration: 30.0
|
|
17
|
+
- fps: 30
|
|
18
|
+
- size: 1920 × 1080
|
|
19
|
+
- palette: #0d1117 #f0f6ff #3b82f6
|
|
20
|
+
- bpm: 120
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Shot 1 — Opening
|
|
25
|
+
- start: 0.0
|
|
26
|
+
- end: 8.0
|
|
27
|
+
- transition_in: fade-from-black
|
|
28
|
+
- transition_out: cut
|
|
29
|
+
- event: The camera pushes slowly through darkness until a single point of light fills the frame.
|
|
30
|
+
- reads:
|
|
31
|
+
- 0.0–3.0: Viewer feels disorientation — where are we?
|
|
32
|
+
- 3.0–8.0: The light resolves into a planet, establishing scale.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Shot 2 — Reveal
|
|
37
|
+
- start: 8.0
|
|
38
|
+
- end: 18.0
|
|
39
|
+
- transition_in: cut
|
|
40
|
+
- transition_out: dissolve
|
|
41
|
+
- event: The planet rotates a quarter-turn, revealing the continent below, and the camera begins a slow descent.
|
|
42
|
+
- reads:
|
|
43
|
+
- 8.0–12.0: Viewer recognises the landmass.
|
|
44
|
+
- 12.0–18.0: Viewer understands we are descending toward a specific location.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Shot 3 — Close
|
|
49
|
+
- start: 18.0
|
|
50
|
+
- end: 30.0
|
|
51
|
+
- transition_in: dissolve
|
|
52
|
+
- transition_out: fade-to-black
|
|
53
|
+
- event: A single window lights up in the city below as all other lights flicker off.
|
|
54
|
+
- reads:
|
|
55
|
+
- 18.0–24.0: Viewer feels the loneliness of the lit window.
|
|
56
|
+
- 24.0–30.0: Viewer understands this story is about one person among millions.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
<!--
|
|
60
|
+
NOTES FOR THE MODEL
|
|
61
|
+
───────────────────
|
|
62
|
+
• "event" is a physical action, not a mood adjective.
|
|
63
|
+
BAD: "A beautiful landscape is shown."
|
|
64
|
+
GOOD: "A wave breaks over the seawall and spray hits the lens."
|
|
65
|
+
|
|
66
|
+
• "reads" timestamps must not overlap:
|
|
67
|
+
BAD: 0.0–2.0 and 1.5–3.0 (overlap at 1.5–2.0)
|
|
68
|
+
GOOD: 0.0–2.0 and 2.0–4.0
|
|
69
|
+
|
|
70
|
+
• Every transition must be named. Recognised values:
|
|
71
|
+
cut fade-from-black fade-to-black dissolve wipe-left wipe-right
|
|
72
|
+
iris-in iris-out cross-dissolve push-left push-right flash dip-to-white
|
|
73
|
+
|
|
74
|
+
• No text means no lower-thirds, title cards, subtitles, or overlays.
|
|
75
|
+
The model cannot write `text()` calls that convey plot information.
|
|
76
|
+
Character names, place names, dates — all must be acted or implied visually.
|
|
77
|
+
-->
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<!--
|
|
7
|
+
FrameForge HTML Compositor Page
|
|
8
|
+
================================
|
|
9
|
+
This page is the render target for headless Playwright captures.
|
|
10
|
+
render.mjs navigates here, injects the virtual-clock shim, then calls
|
|
11
|
+
window.__seek(t) once per frame to advance to time t.
|
|
12
|
+
|
|
13
|
+
DO NOT add async resource loads (remote fonts, CDN scripts) — all assets
|
|
14
|
+
must be local so the page reaches `networkidle` deterministically.
|
|
15
|
+
|
|
16
|
+
The #stage div is required. engine.js appends all layer divs into it.
|
|
17
|
+
Its dimensions are set by reel() in edit.js and matched by
|
|
18
|
+
page.setViewportSize() in render.mjs.
|
|
19
|
+
-->
|
|
20
|
+
<link rel="stylesheet" href="reel/engine.css">
|
|
21
|
+
<style>
|
|
22
|
+
/* Reset */
|
|
23
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
24
|
+
|
|
25
|
+
html, body {
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
background: #000;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* #stage is sized by reel() in edit.js (via CSS custom props set inline).
|
|
33
|
+
Fallback: 1920×1080. render.mjs also calls page.setViewportSize(). */
|
|
34
|
+
#stage {
|
|
35
|
+
position: relative;
|
|
36
|
+
width: var(--reel-width, 1920px);
|
|
37
|
+
height: var(--reel-height, 1080px);
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
background: #000;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Layer divs injected by engine.js */
|
|
43
|
+
#stage > [data-layer] {
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 0;
|
|
46
|
+
left: 0;
|
|
47
|
+
width: 100%;
|
|
48
|
+
height: 100%;
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Captions overlay — placed by engine.js captions() */
|
|
53
|
+
#caption-overlay {
|
|
54
|
+
position: absolute;
|
|
55
|
+
bottom: 80px;
|
|
56
|
+
left: 50%;
|
|
57
|
+
transform: translateX(-50%);
|
|
58
|
+
z-index: 9999;
|
|
59
|
+
text-align: center;
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body>
|
|
65
|
+
<!-- The compositor stage. All layers are appended here by engine.js. -->
|
|
66
|
+
<div id="stage"></div>
|
|
67
|
+
|
|
68
|
+
<!--
|
|
69
|
+
edit.js is the author-written animation file.
|
|
70
|
+
It imports from ./reel/engine.js and calls:
|
|
71
|
+
reel({ width, height, fps, duration, title })
|
|
72
|
+
layer('name').on(t => ({ ...styles }))
|
|
73
|
+
at(t, element)
|
|
74
|
+
etc.
|
|
75
|
+
|
|
76
|
+
The module type is required — engine.js uses ES module exports.
|
|
77
|
+
-->
|
|
78
|
+
<script type="module" src="edit.js"></script>
|
|
79
|
+
</body>
|
|
80
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# pipeline/briefings/diagram-author.md
|
|
2
|
+
# Loaded by diagram-author subagents. Read this fully before writing diagram code.
|
|
3
|
+
|
|
4
|
+
## Your role
|
|
5
|
+
|
|
6
|
+
You are a **diagram-author** subagent. You produce one or more animated diagram
|
|
7
|
+
scenes (flowcharts, data visualisations, timeline graphics, explainer layouts).
|
|
8
|
+
|
|
9
|
+
## Constraints
|
|
10
|
+
|
|
11
|
+
1. **Read ANIMATION_GUIDE.md in full** before writing code.
|
|
12
|
+
2. Do not edit shared helpers (`reel/engine.js`, etc.).
|
|
13
|
+
3. Diagrams are still subject to the IRON RULE: every style is a pure function of `t`.
|
|
14
|
+
4. **No rotateY / perspective** on flat diagrams — breaks layout.
|
|
15
|
+
5. **No photoreal expectations** — diagrams are motion graphics. Flat fills, SVG,
|
|
16
|
+
Canvas 2D, or Three.js orthographic only.
|
|
17
|
+
|
|
18
|
+
## Recommended stack by diagram type
|
|
19
|
+
|
|
20
|
+
| Diagram type | Recommended |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Flowchart / boxes and arrows | SVG + GSAP timeline `.seek(t)` |
|
|
23
|
+
| Data chart (bar, line, scatter) | Canvas 2D + lerp() |
|
|
24
|
+
| Network / force-directed | Precomputed positions + lerp() |
|
|
25
|
+
| Timeline / Gantt | CSS absolute positioning + `layer()` |
|
|
26
|
+
| 3D architecture | Three.js orthographic + `camera.position` driven by `t` |
|
|
27
|
+
|
|
28
|
+
## GSAP pattern (paused + seeked)
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import gsap from 'https://cdn.skypack.dev/gsap'
|
|
32
|
+
// Build the timeline PAUSED — the renderer drives it
|
|
33
|
+
const tl = gsap.timeline({ paused: true })
|
|
34
|
+
tl.to('#box', { x: 200, duration: 1 })
|
|
35
|
+
layer('diagram').on(t => { tl.seek(t); return {} })
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Never call `tl.play()` or `tl.resume()` — the virtual clock shim drives `rAF`,
|
|
39
|
+
but GSAP's internal clock is separate and will desync if started.
|
|
40
|
+
|
|
41
|
+
## Returning
|
|
42
|
+
|
|
43
|
+
Reply with the complete diagram scene file and a storyboard still.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# pipeline/briefings/orchestrator.md
|
|
2
|
+
# Loaded by the orchestrator agent. Read this fully before spawning subagents.
|
|
3
|
+
|
|
4
|
+
## Your role
|
|
5
|
+
|
|
6
|
+
You are the **orchestrator**. You own the full production timeline and the final output.
|
|
7
|
+
You spawn scene-author and diagram-author subagents, integrate their output, and run
|
|
8
|
+
the verification loop.
|
|
9
|
+
|
|
10
|
+
## Order of operations
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
1. Brief → storyboard (STORYBOARD.md) — STOP, await human approval
|
|
14
|
+
2. Storyboard passes all 4 gates (storyboard-validator.mjs)
|
|
15
|
+
3. TTS first: generate audio, get word-level timestamps (transcribe.py)
|
|
16
|
+
4. Assign chapters to scene-author / diagram-author subagents
|
|
17
|
+
5. Collect scenes — each must include a storyboard still
|
|
18
|
+
6. Run look.mjs (preflight DOM check)
|
|
19
|
+
7. Run render.mjs sheet (one sheet per shot)
|
|
20
|
+
8. Review sheets, request patches from the responsible subagent
|
|
21
|
+
9. Run render.mjs full (full encode, all workers)
|
|
22
|
+
10. Run verify.py (7-gate QC)
|
|
23
|
+
11. If QC fails: identify the responsible chapter, re-assign to a fresh subagent
|
|
24
|
+
12. Mux audio: render.mjs mux --audio=audio/final_mix.wav
|
|
25
|
+
13. Final verify.py pass
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Subagent rules
|
|
29
|
+
|
|
30
|
+
- Each subagent receives a briefing from `pipeline/briefings/<role>.md`.
|
|
31
|
+
- **Subagents may not edit shared helpers.** If a subagent proposes an edit to
|
|
32
|
+
`reel/engine.js` or `scripts/render.mjs`, reject it and ask for a chapter-local
|
|
33
|
+
workaround.
|
|
34
|
+
- Run verification with subagents: 2 subagents at a time, each scanning 10 frames
|
|
35
|
+
(every 5 frames of the rendered sequence).
|
|
36
|
+
- Chapter seams are the weakest point. Always run a strip over the last 0.5s of
|
|
37
|
+
one chapter and the first 0.5s of the next to catch missing anticipation frames.
|
|
38
|
+
|
|
39
|
+
## Spend gate
|
|
40
|
+
|
|
41
|
+
If `video_job.py` is running this production, it enforces `--max-usd`. You must call
|
|
42
|
+
`estimate_cost()` before the visuals phase. If the estimate would exceed the cap,
|
|
43
|
+
reduce the chapter count or shorten scenes before proceeding.
|
|
44
|
+
|
|
45
|
+
## Quality gate pass criteria
|
|
46
|
+
|
|
47
|
+
`verify.py` checks:
|
|
48
|
+
1. Output file exists and is non-empty
|
|
49
|
+
2. Duration within 2% of spec
|
|
50
|
+
3. Resolution matches spec
|
|
51
|
+
4. No corrupt frames (mean pixel value > 1, < 254)
|
|
52
|
+
5. Audio track present in muxed file
|
|
53
|
+
6. BT.709 colour tags present
|
|
54
|
+
7. `-faststart` moov atom at file head
|
|
55
|
+
|
|
56
|
+
All 7 must pass before the job is marked `done`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pipeline/briefings/scene-author.md
|
|
2
|
+
# Loaded by scene-author subagents — read this fully before writing any scene code.
|
|
3
|
+
|
|
4
|
+
## Your role
|
|
5
|
+
|
|
6
|
+
You are a **scene-author** subagent. Your only job is to write the scene file(s)
|
|
7
|
+
assigned to you by the orchestrator. You do not edit shared helpers.
|
|
8
|
+
|
|
9
|
+
## Hard constraints
|
|
10
|
+
|
|
11
|
+
1. **Read ANIMATION_GUIDE.md in full** before you write a single line of scene code.
|
|
12
|
+
2. **Do not edit** `reel/engine.js`, `reel/engine.css`, `scripts/render.mjs`,
|
|
13
|
+
`scripts/look.mjs`, or any other file outside your assigned chapter directory.
|
|
14
|
+
3. Every style property must be a **pure function of `t`**. No `setTimeout`,
|
|
15
|
+
no `Math.random()`, no module-level mutable state.
|
|
16
|
+
4. After writing, run:
|
|
17
|
+
```bash
|
|
18
|
+
node scripts/render.mjs storyboard
|
|
19
|
+
```
|
|
20
|
+
Attach the output PNG to your reply. The orchestrator will not accept your scene
|
|
21
|
+
without a storyboard still.
|
|
22
|
+
5. If `cuePhrase()` throws a fatal error, your scene has a missing voiceover phrase.
|
|
23
|
+
Fix the phrase match before returning.
|
|
24
|
+
|
|
25
|
+
## File naming
|
|
26
|
+
|
|
27
|
+
Your chapter file is: `edit_chapter_N.js` where N is your chapter number.
|
|
28
|
+
The orchestrator's `index.html` `<script>` tag already imports it. Do not create
|
|
29
|
+
additional entry points.
|
|
30
|
+
|
|
31
|
+
## Transition seams
|
|
32
|
+
|
|
33
|
+
The orchestrator's storyboard specifies the transition_in and transition_out for your
|
|
34
|
+
chapter. Implement them exactly. The fade/dissolve between your chapter and the next
|
|
35
|
+
is your responsibility on the out-side; the next chapter handles its in-side.
|
|
36
|
+
|
|
37
|
+
## Returning
|
|
38
|
+
|
|
39
|
+
Reply with:
|
|
40
|
+
1. The complete scene file contents (no diffs, no fragments).
|
|
41
|
+
2. The storyboard still (PNG attached or path printed).
|
|
42
|
+
3. Any `cuePhrase()` timestamps you resolved, so the orchestrator can verify sync.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine.css — FrameForge HTML Compositor Base Reset + Design Tokens
|
|
3
|
+
*
|
|
4
|
+
* Loaded by index.html before edit.js.
|
|
5
|
+
* No animation or transition rules — every visual state is driven by engine.js seek(t).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* ── Design tokens ─────────────────────────────────────────────────────────── */
|
|
9
|
+
|
|
10
|
+
:root {
|
|
11
|
+
/* Colours */
|
|
12
|
+
--bg: #0d1117;
|
|
13
|
+
--surface: #161b22;
|
|
14
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
15
|
+
--primary: #ffffff;
|
|
16
|
+
--secondary: rgba(255, 255, 255, 0.6);
|
|
17
|
+
--accent: #3b82f6;
|
|
18
|
+
--accent2: #7c5cd8;
|
|
19
|
+
--success: #22c55e;
|
|
20
|
+
--warning: #f59e0b;
|
|
21
|
+
--danger: #ef4444;
|
|
22
|
+
|
|
23
|
+
/* Typography scale */
|
|
24
|
+
--font-body: -apple-system, "Segoe UI", system-ui, sans-serif;
|
|
25
|
+
--font-mono: "Fira Code", "Cascadia Code", "Consolas", monospace;
|
|
26
|
+
--font-display: "Archivo", "Segoe UI", system-ui, sans-serif;
|
|
27
|
+
|
|
28
|
+
/* Spacing */
|
|
29
|
+
--gap-sm: 8px;
|
|
30
|
+
--gap-md: 16px;
|
|
31
|
+
--gap-lg: 32px;
|
|
32
|
+
--gap-xl: 64px;
|
|
33
|
+
|
|
34
|
+
/* Safe zones (match FFmpeg drawtext safe area) */
|
|
35
|
+
--safe-top: 120px;
|
|
36
|
+
--safe-bottom: 120px;
|
|
37
|
+
--safe-left: 80px;
|
|
38
|
+
--safe-right: 80px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ── Full reset ─────────────────────────────────────────────────────────────── */
|
|
42
|
+
|
|
43
|
+
*, *::before, *::after {
|
|
44
|
+
box-sizing: border-box;
|
|
45
|
+
margin: 0;
|
|
46
|
+
padding: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
html, body {
|
|
50
|
+
width: 100%;
|
|
51
|
+
height: 100%;
|
|
52
|
+
overflow: hidden;
|
|
53
|
+
background: var(--bg);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
body {
|
|
57
|
+
font-family: var(--font-body);
|
|
58
|
+
-webkit-font-smoothing: antialiased;
|
|
59
|
+
-moz-osx-font-smoothing: grayscale;
|
|
60
|
+
color: var(--primary);
|
|
61
|
+
/* Disable ALL browser animations — seek(t) owns every frame */
|
|
62
|
+
animation: none !important;
|
|
63
|
+
transition: none !important;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ── Stage ─────────────────────────────────────────────────────────────────── */
|
|
67
|
+
|
|
68
|
+
#stage {
|
|
69
|
+
position: relative;
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
background: var(--bg);
|
|
72
|
+
/* No transform on the stage itself — Playwright screenshots the viewport */
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ── Layer base ─────────────────────────────────────────────────────────────── */
|
|
76
|
+
|
|
77
|
+
[data-layer] {
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
pointer-events: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* ── Typography utilities ───────────────────────────────────────────────────── */
|
|
84
|
+
|
|
85
|
+
.text-display {
|
|
86
|
+
font-family: var(--font-display);
|
|
87
|
+
font-weight: 800;
|
|
88
|
+
letter-spacing: -0.02em;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.text-body {
|
|
92
|
+
font-family: var(--font-body);
|
|
93
|
+
font-weight: 400;
|
|
94
|
+
line-height: 1.6;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.text-mono {
|
|
98
|
+
font-family: var(--font-mono);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ── Utility classes ────────────────────────────────────────────────────────── */
|
|
102
|
+
|
|
103
|
+
.center-x {
|
|
104
|
+
left: 50%;
|
|
105
|
+
transform: translateX(-50%);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.center-y {
|
|
109
|
+
top: 50%;
|
|
110
|
+
transform: translateY(-50%);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.center-xy {
|
|
114
|
+
top: 50%;
|
|
115
|
+
left: 50%;
|
|
116
|
+
transform: translate(-50%, -50%);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ── Image defaults ─────────────────────────────────────────────────────────── */
|
|
120
|
+
|
|
121
|
+
img {
|
|
122
|
+
display: block;
|
|
123
|
+
max-width: 100%;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ── Scrollbar hide (for overflow containers) ───────────────────────────────── */
|
|
127
|
+
|
|
128
|
+
::-webkit-scrollbar { display: none; }
|
|
129
|
+
* { scrollbar-width: none; }
|
|
130
|
+
|
|
131
|
+
/* ── Font imports — loaded from CDN in index.html; this is a fallback stack ── */
|
|
132
|
+
|
|
133
|
+
/* Archivo (display) */
|
|
134
|
+
@font-face {
|
|
135
|
+
font-family: 'Archivo';
|
|
136
|
+
font-style: normal;
|
|
137
|
+
font-weight: 400 900;
|
|
138
|
+
font-display: block;
|
|
139
|
+
/* Served by index.html via Google Fonts; this block avoids FOUT in preview */
|
|
140
|
+
}
|