@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.
Files changed (48) hide show
  1. package/.mcp.json +2 -4
  2. package/CHANGELOG.md +1 -1
  3. package/CLAUDE.md +104 -26
  4. package/README.md +16 -12
  5. package/SECURITY.md +1 -3
  6. package/package.json +7 -3
  7. package/plugins/video-pipeline/.claude-plugin/plugin.json +19 -0
  8. package/plugins/video-pipeline/skills/html-renderer/SKILL.md +292 -0
  9. package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +308 -0
  10. package/plugins/video-pipeline/skills/video-pipeline/assets/project/ANIMATION_GUIDE.md +257 -0
  11. package/plugins/video-pipeline/skills/video-pipeline/assets/project/STORYBOARD.md +77 -0
  12. package/plugins/video-pipeline/skills/video-pipeline/assets/project/index.html +80 -0
  13. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/diagram-author.md +43 -0
  14. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +56 -0
  15. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +42 -0
  16. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.css +140 -0
  17. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +870 -0
  18. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +138 -0
  19. package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +167 -0
  20. package/plugins/video-pipeline/skills/video-pipeline/references/providers.md +163 -0
  21. package/scripts/db-migrate.js +112 -0
  22. package/scripts/deep-validate.js +326 -0
  23. package/scripts/generate-initial-assets.js +342 -0
  24. package/scripts/html-renderer/new-project.mjs +408 -0
  25. package/scripts/html-renderer/transcribe.py +294 -0
  26. package/src/asset-library/README.md +2 -2
  27. package/src/asset-library/asset-library.js +1 -1
  28. package/src/cli/make.js +52 -71
  29. package/src/cli/setup-keys.js +15 -17
  30. package/src/fact-checker/review-gate.js +5 -1
  31. package/src/harness/bin/harness.mjs +324 -0
  32. package/src/harness/lib/browser.mjs +137 -0
  33. package/src/harness/lib/capture.mjs +161 -0
  34. package/src/harness/lib/encode.mjs +89 -0
  35. package/src/harness/lib/jobs.mjs +135 -0
  36. package/src/harness/lib/pixels.mjs +137 -0
  37. package/src/harness/lib/sheets.mjs +164 -0
  38. package/src/harness/lib/verify-purity.mjs +119 -0
  39. package/src/harness/test/fixtures/broken-scene.html +27 -0
  40. package/src/harness/test/fixtures/reference-scene.html +23 -0
  41. package/src/harness/test/fixtures/sample-script.json +18 -0
  42. package/src/mcp-server/server.js +285 -144
  43. package/src/pipeline/telemetry.js +1 -0
  44. package/src/utils/log-redaction.js +5 -3
  45. package/src/video-renderer/renderer.js +12 -4
  46. package/src/worker/server-render-free.js +2 -3
  47. package/src/workflows/assets.js +20 -43
  48. 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
+ }