@amg555/frameforge-mcp 2.4.10 → 2.7.1

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 (104) hide show
  1. package/.claude-plugin/marketplace.json +22 -4
  2. package/CHANGELOG.md +174 -0
  3. package/CLAUDE.md +192 -9
  4. package/README.md +47 -6
  5. package/package.json +4 -3
  6. package/plugins/video-pipeline/.claude-plugin/plugin.json +8 -2
  7. package/plugins/video-pipeline/skills/.upstream-skills.json +143 -0
  8. package/plugins/video-pipeline/skills/html-renderer/SKILL.md +59 -15
  9. package/plugins/video-pipeline/skills/html-renderer/references/animation-patterns.md +609 -0
  10. package/plugins/video-pipeline/skills/html-renderer/references/character-animation.md +388 -0
  11. package/plugins/video-pipeline/skills/html-renderer/references/collage-broll.md +41 -0
  12. package/plugins/video-pipeline/skills/html-renderer/references/design.md +217 -0
  13. package/plugins/video-pipeline/skills/html-renderer/references/directing.md +165 -0
  14. package/plugins/video-pipeline/skills/html-renderer/references/earshot-audio.md +201 -0
  15. package/plugins/video-pipeline/skills/html-renderer/references/engine-api.md +480 -0
  16. package/plugins/video-pipeline/skills/html-renderer/references/film-styles.md +95 -0
  17. package/plugins/video-pipeline/skills/html-renderer/references/hyperframes-motion.md +208 -0
  18. package/plugins/video-pipeline/skills/html-renderer/references/onetake-camera.md +59 -0
  19. package/plugins/video-pipeline/skills/html-renderer/references/procedural-sfx.md +135 -0
  20. package/plugins/video-pipeline/skills/html-renderer/references/remotion-patterns.md +159 -0
  21. package/plugins/video-pipeline/skills/video-pipeline/SKILL.md +19 -2
  22. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/orchestrator.md +15 -0
  23. package/plugins/video-pipeline/skills/video-pipeline/assets/project/pipeline/briefings/scene-author.md +21 -0
  24. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/engine.js +1293 -20
  25. package/plugins/video-pipeline/skills/video-pipeline/assets/project/reel/virtual-clock.js +36 -6
  26. package/plugins/video-pipeline/skills/video-pipeline/references/mcp-tools.md +126 -2
  27. package/scripts/deep-validate.js +52 -2
  28. package/src/audio-mixer/mixer.js +594 -63
  29. package/src/cli/doctor.js +36 -2
  30. package/src/cli/make.js +113 -9
  31. package/src/harness/bin/harness.mjs +127 -5
  32. package/src/harness/lib/capture.mjs +215 -192
  33. package/src/harness/lib/verify-quality.mjs +47 -7
  34. package/src/mcp-server/server.js +1015 -59
  35. package/src/skills/sync-upstream.js +290 -0
  36. package/src/utils/media-downloader.js +162 -0
  37. package/src/utils/transcribe.js +341 -0
  38. package/src/video-editor/editor.js +96 -11
  39. package/src/workflows/ingest.js +10 -0
  40. package/.bob/artifacts/enterprise-ai-video-agent-platform-agnostic-implementation-guide.html +0 -464
  41. package/out/check/purity_report.json +0 -79
  42. package/out/frames/f000000.jpg +0 -0
  43. package/out/frames/f000001.jpg +0 -0
  44. package/out/frames/f000002.jpg +0 -0
  45. package/out/frames/f000003.jpg +0 -0
  46. package/out/frames/f000004.jpg +0 -0
  47. package/out/frames/f000005.jpg +0 -0
  48. package/out/frames/f000006.jpg +0 -0
  49. package/out/frames/f000007.jpg +0 -0
  50. package/out/frames/f000008.jpg +0 -0
  51. package/out/frames/f000009.jpg +0 -0
  52. package/out/frames/f000010.jpg +0 -0
  53. package/out/frames/f000011.jpg +0 -0
  54. package/out/frames/f000012.jpg +0 -0
  55. package/out/frames/f000013.jpg +0 -0
  56. package/out/frames/f000014.jpg +0 -0
  57. package/out/frames/f000015.jpg +0 -0
  58. package/out/frames/f000016.jpg +0 -0
  59. package/out/frames/f000017.jpg +0 -0
  60. package/out/frames/f000018.jpg +0 -0
  61. package/out/frames/f000019.jpg +0 -0
  62. package/out/frames/f000020.jpg +0 -0
  63. package/out/frames/f000021.jpg +0 -0
  64. package/out/frames/f000022.jpg +0 -0
  65. package/out/frames/f000023.jpg +0 -0
  66. package/out/frames/f000024.jpg +0 -0
  67. package/out/frames/f000025.jpg +0 -0
  68. package/out/frames/f000026.jpg +0 -0
  69. package/out/frames/f000027.jpg +0 -0
  70. package/out/frames/f000028.jpg +0 -0
  71. package/out/frames/f000029.jpg +0 -0
  72. package/out/frames/f000030.jpg +0 -0
  73. package/out/frames/f000031.jpg +0 -0
  74. package/out/frames/f000032.jpg +0 -0
  75. package/out/frames/f000033.jpg +0 -0
  76. package/out/frames/f000034.jpg +0 -0
  77. package/out/frames/f000035.jpg +0 -0
  78. package/out/frames/f000036.jpg +0 -0
  79. package/out/frames/f000037.jpg +0 -0
  80. package/out/frames/f000038.jpg +0 -0
  81. package/out/frames/f000039.jpg +0 -0
  82. package/out/frames/f000040.jpg +0 -0
  83. package/out/frames/f000041.jpg +0 -0
  84. package/out/frames/f000042.jpg +0 -0
  85. package/out/frames/f000043.jpg +0 -0
  86. package/out/frames/f000044.jpg +0 -0
  87. package/out/frames/f000045.jpg +0 -0
  88. package/out/frames/f000046.jpg +0 -0
  89. package/out/frames/f000047.jpg +0 -0
  90. package/out/frames/f000048.jpg +0 -0
  91. package/out/frames/f000049.jpg +0 -0
  92. package/out/frames/f000050.jpg +0 -0
  93. package/out/frames/f000051.jpg +0 -0
  94. package/out/frames/f000052.jpg +0 -0
  95. package/out/frames/f000053.jpg +0 -0
  96. package/out/frames/f000054.jpg +0 -0
  97. package/out/frames/f000055.jpg +0 -0
  98. package/out/frames/f000056.jpg +0 -0
  99. package/out/frames/f000057.jpg +0 -0
  100. package/out/frames/f000058.jpg +0 -0
  101. package/out/frames/f000059.jpg +0 -0
  102. package/src/harness/test/fixtures/broken-scene.html +0 -27
  103. package/src/harness/test/fixtures/reference-scene.html +0 -23
  104. package/src/harness/test/fixtures/sample-script.json +0 -18
@@ -0,0 +1,388 @@
1
+ # Character Animation — ClaudeAnimationBase Full Guide
2
+
3
+ Adapted from `JohnHeibel/ClaudeAnimationBase` `ANIMATION_GUIDE.md`.
4
+ The full hand-painted animation method for code-driven character films.
5
+ 2.9k stars. All test videos generated with Opus 5.5 on `xhigh` reasoning in Claude Code.
6
+
7
+ ---
8
+
9
+ ## The Three Goals
10
+
11
+ Every rule serves one of three goals:
12
+
13
+ - **Handmade.** The video looks like someone painted it by hand, frame by frame — brush strokes, ink lines that boil, flat 2D, no lettering.
14
+ - **Alive.** Something is always moving and something is always happening. Faces act instead of snapping, and characters are big enough to feel.
15
+ - **One piece.** It's one short film, not a pile of clips. Plan it before you draw it, and link every scene to the next.
16
+
17
+ Underneath all three: the viewer has to be able to follow it. **Timing (rule 4) is the rule models get wrong most often.**
18
+
19
+ ---
20
+
21
+ ## The Rules
22
+
23
+ ### Rule 1. The Medium Is Solid: Brush Strokes, Flat 2D, Boil
24
+
25
+ - **Paint with brush primitives.** Characters get flat colour plus an ink outline. Backgrounds get soft watercolour shapes.
26
+ - **Never use plain CSS shapes** (rect, ellipse, fill): they look like 2000s Flash. Use SVG path or canvas brushwork.
27
+ - **The linework boils.** Re-seed the PRNG 12 times a second (`BOIL = Math.floor(t * 12)`), so every drawing wobbles slightly like hand-drawn animation. That's the look — don't fight it.
28
+ - For anything that must stay put (star positions, tuft heights), use `hash(i)` — a seeded deterministic value per element.
29
+ - **Everything is flat 2D.** Never project 3D. Depth comes from overlap, scale, and colour (farther = smaller, bluer, paler). Never from a projection.
30
+ - **Soft palette, no pure black or white.** Use palette ink for black, palette cream for white. Keep colours soft and harmonious.
31
+
32
+ ```javascript
33
+ // PRNG boil seed — quantize to 12fps grid
34
+ const BOIL = Math.floor(t * 12);
35
+ function boil(seed, amplitude = 1) {
36
+ return Math.sin(seed * 19.37 + BOIL * 2.41) * amplitude;
37
+ }
38
+ // Use: stable elements use hash(i), boiling elements use boil(seed)
39
+ function hash(i) { return Math.sin(i * 127.1 + 311.7) * 43758.5453 % 1; }
40
+ ```
41
+
42
+ ### Rule 2. No Text
43
+
44
+ - Show it, don't write it. No captions, no titles, no labels on objects, no speech bubbles with words.
45
+ - Character reactions are painted marks, never letters: `!`, `?`, zzz, sweat, hearts, a bulb, a rain cloud.
46
+ - A sign that repeats the story is the classic failure. If the character holds a sign saying "I'm lost", the shot has failed. Show the character being lost: looking left, then right, the map upside down, a sweat drop.
47
+ - If a single word is truly needed: paint it into the scene, keep it to one or two words, use it once.
48
+
49
+ ### Rule 3. Something Happens in Every Scene
50
+
51
+ - **Every shot needs an event.** Something changes between its first frame and its last. "Character stands in a meadow being cute" is not a shot.
52
+ - **One focal action at a time.** Stage it with a clear silhouette.
53
+ - **Cause, then reaction.** When something happens, the character reacts: a take, an emotion change, a turn toward it.
54
+ - **Pay it off.** Whatever you set up (a door, a sandwich, a noise) gets resolved on screen.
55
+
56
+ ### Rule 4. Timing: Model the Viewer
57
+
58
+ Timing is where generated animation fails most often: everything moves at one brisk speed, events pile on top of each other, and moments are over before anyone understands them.
59
+
60
+ **For every moment, ask: what does the viewer need to understand, and how long will that take them?**
61
+
62
+ - **Write the reads.** For each shot, list in order what the viewer has to understand. Each item is a *read*. Every read needs:
63
+ - Time for the eye to find it
64
+ - Time to understand it
65
+ - A moment to register before the next thing starts
66
+ - Small, distant, fast, or subtle things take longer to find than big, central, obvious ones
67
+ - **One read at a time.** Don't start a new read while the viewer is still taking in the last one.
68
+ - **Fast actions, slow meanings.** A motion can be very quick if it's anticipated, but what it means needs held time.
69
+ - Anticipation tells the viewer where to look before the action
70
+ - The hold after lets them understand it
71
+ - Move quickly through what doesn't matter; spend time on what does
72
+ - That contrast between quick and held is what gives rhythm
73
+ - **Lead the eye.** The viewer looks at whatever moves, is bright, is big, or is being looked at. Before an important read, get their eye to the right place first.
74
+ - **Let the reads set the length.** A shot is as long as its reads need. A shot that's over too fast had its reads squeezed; a padded shot had too few reads.
75
+
76
+ ### Rule 5. Alive
77
+
78
+ - **Nothing is ever still.** Every emotion has its own idle motion; cameras drift or push; environments are alive; linework boils.
79
+ - **Faces act, they never snap.** Change moods through the `emotions()` system — anticipation, squint, take, overshoot around every change.
80
+ - **Move like a cartoon, not a machine.** Every move follows classic animation principles (see below).
81
+ - **Character is big.** In a medium shot, the character is ~20–28 units. In a close-up: 40–70. Tiny characters (< 12 units) only for wide establishing shots.
82
+ - **Everything moves on a beat.** BPM drives every idle, bounce, and dance, so the whole film shares one pulse.
83
+
84
+ ### Rule 6. Transitions Always
85
+
86
+ - **Every seam gets a transition**: into the first shot, between every pair of shots, out of the last.
87
+ - Never start on a hard frame, never just stop.
88
+ - **Pick a transition that belongs to the story**:
89
+ - Brush wipe (fat strokes cover the frame and drag off)
90
+ - Iris or shaped iris
91
+ - Whip pan with a smear
92
+ - Match cut (same shape or motion across the cut)
93
+ - Cut on action (cut mid-move, finish in next shot)
94
+ - Camera move that carries through into the next shot
95
+ - Fade or push from paper or black
96
+ - A plain cut is fine only when it's on action or a deliberate smash cut.
97
+ - **Changes inside a shot are transitions too**: emotions, turns, prop arrivals and departures — always on arcs.
98
+
99
+ ### Rule 7. One Piece: Vision Before Any Code
100
+
101
+ - **Storyboard first**, in writing, before writing any scene code.
102
+ - **One world.** Pick a palette and setting that carries through, with a colour arc across the video.
103
+ - **One thread.** Story has a beginning, middle, and end. Emotional arc is planned across the whole video, not per shot.
104
+ - **Rhyme the ending with the opening**: same place, pose, or motif — changed. Makes the film feel whole.
105
+ - **Link scenes**: motion continues across cuts, screen direction stays consistent.
106
+
107
+ ---
108
+
109
+ ## Animation Principles (The Classics Applied to Code)
110
+
111
+ Motion written as code comes out mechanical: every part moves at once, on the same curve, by the same amount. These principles fix that:
112
+
113
+ | Principle | How to Apply in Code |
114
+ |-----------|---------------------|
115
+ | **Anticipation** | Before a big move, a small move the opposite way. `jump()` builds this in. |
116
+ | **Squash and stretch** | Bodies squash on impact, stretch when fast — keeping volume. Parameter `sq`. |
117
+ | **Slow in, slow out** | Run progress through `easeIn()`, `easeOut()`, `backOut()` — never plain `lerp` over time |
118
+ | **Weight** | How something starts and stops says what it weighs. Heavy = slow start, slow stop, little bounce. |
119
+ | **Arcs** | Living things move on arcs, not lines. `arcPt(p0, p1, h, k)` |
120
+ | **Overlapping action** | Eyes lead, body follows, arms/hats/props drag behind. Offset each part's timing from the thing it hangs off. `spring()` for the settle. |
121
+ | **Avoid twinning** | Give one arm the action and the other something smaller. Offset timings and phases between characters. |
122
+ | **Exaggeration** | Push poses, takes, squash, and leans further than feels natural. Subtle reads as nothing. |
123
+ | **Strong key poses** | Each shot's storytelling poses should read as stills, with clear silhouette. |
124
+ | **Show the thought** | A character notices, thinks, then acts. Eyes move first. |
125
+ | **Secondary action** | Small actions that support the main one (hat bobbing, emote popping). Never compete with main action. |
126
+
127
+ ---
128
+
129
+ ## Storyboard Format
130
+
131
+ Write `STORYBOARD.md` before any scene code:
132
+
133
+ ```
134
+ Logline: one sentence. Character wants ___, but ___, so ___.
135
+ World: setting, palette, light, how the colour changes across the video.
136
+ Motif: the thing that recurs and pays off.
137
+ Character's arc: the emotion keys across the whole video.
138
+
139
+ Shots:
140
+ A start–end [transition in: ___] what's seen · the EVENT · character's reaction · camera
141
+ reads: start–end the first thing the viewer must understand
142
+ start–end the next one (where is the viewer's eye when it starts?)
143
+ ...
144
+ B start–end [transition: ___] ...
145
+ ...
146
+ [transition out: ___]
147
+ ```
148
+
149
+ **Reads are the timing sheet.** Give each one a start and end. Make sure each has time to be found and understood. Make sure no two important reads overlap.
150
+
151
+ Check the storyboard:
152
+ - Is there an event in every shot?
153
+ - Does every read have time to land before the next starts?
154
+ - Is there a transition at every seam?
155
+ - Is there any text anywhere?
156
+ - Does the ending rhyme with the opening?
157
+
158
+ ---
159
+
160
+ ## Frames Are Pure Functions of Time
161
+
162
+ **The most important technical constraint:**
163
+
164
+ - Frames render **in parallel and out of order**
165
+ - A shot is `fn(t, lt, dur)` and must draw the **same frame for the same `t`**, every time
166
+ - No state carried between frames; no counters; no `Math.random()`; no physics that integrates frame by frame
167
+ - Compute **everything from `t`**, in closed form
168
+
169
+ ```javascript
170
+ // CORRECT — pure function of lt (local time)
171
+ layer('char').on((t, lt, dur) => {
172
+ const step = Math.floor(lt * 12); // 12fps boil step
173
+ const bx = Math.sin(7.3 * 19.37 + step * 2.41) * 2.4;
174
+ const emotion = lt < 1.5 ? 'neutral' : 'happy'; // computed from lt, not state
175
+ return { transform: `translateX(${bx}px)` };
176
+ });
177
+
178
+ // WRONG — global state carries between frames
179
+ let emotionState = 'neutral'; // ← never do this
180
+ layer('char').on((t, lt, dur) => {
181
+ if (lt > 1.5) emotionState = 'happy'; // ← breaks determinism
182
+ return { emotion: emotionState };
183
+ });
184
+ ```
185
+
186
+ **Seed each animated element separately.** A moving element uses a different amount of randomness each frame, which shifts the stream for everything drawn after it. Give each element a unique seed:
187
+
188
+ ```javascript
189
+ function elementBoil(seed) {
190
+ const step = Math.floor(t * 12);
191
+ return {
192
+ dx: Math.sin(seed * 19.37 + step * 2.41) * 2.4,
193
+ dy: Math.cos(seed * 11.29 + step * 1.87) * 1.8,
194
+ rot: Math.sin(seed * 13.71 + step * 0.79) * 1.8,
195
+ sc: 1.0 + Math.sin(seed * 7.11 + step * 3.14) * 0.025,
196
+ };
197
+ }
198
+ // Usage: const b = elementBoil(7.3); // seed 7.3 for this element
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Time and Motion Helpers
204
+
205
+ ```javascript
206
+ // Progress through [a, b] → 0..1
207
+ function seg(t, a, b) { return Math.min(1, Math.max(0, (t - a) / (b - a))); }
208
+
209
+ // Keyframes — values may be arrays
210
+ function kf(t, keys, ease = (x => x)) {
211
+ for (let i = 0; i < keys.length - 1; i++) {
212
+ const [t0, v0] = keys[i], [t1, v1] = keys[i+1];
213
+ if (t >= t0 && t <= t1) {
214
+ const p = ease(seg(t, t0, t1));
215
+ return Array.isArray(v0) ? v0.map((v, j) => v + (v1[j] - v) * p) : v0 + (v1 - v0) * p;
216
+ }
217
+ }
218
+ return keys[keys.length - 1][1];
219
+ }
220
+
221
+ // Easing
222
+ const easeOut = t => 1 - Math.pow(1 - t, 3);
223
+ const easeIn = t => t * t * t;
224
+ const ease = t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
225
+ const backOut = t => 1 + 2.70158 * Math.pow(t - 1, 3) + 1.70158 * Math.pow(t - 1, 2);
226
+
227
+ // Rhythm
228
+ // BEAT = 60 / BPM (seconds per beat)
229
+ // pulse(t, k = BEAT) → 1.0 on each beat, then decays
230
+ function pulse(t, k) { return Math.max(0, 1 - (t % k) / (k * 0.7)); }
231
+
232
+ // Jump: crouch → stretch → arc → squash-land
233
+ // Returns {dy, sq} — add to character's pose
234
+ function jump(t, t0, t1, h) {
235
+ if (t < t0 || t > t1 + 0.3) return { dy: 0, sq: 0 };
236
+ const p = seg(t, t0, t1);
237
+ const arc = Math.sin(p * Math.PI); // parabolic arc
238
+ const land = t > t1 ? Math.max(0, 1 - seg(t, t1, t1 + 0.3) * 3) : 0;
239
+ return { dy: -h * arc + land * 8, sq: -arc * 0.12 + land * 0.18 };
240
+ }
241
+
242
+ // Spring settle after an event
243
+ function spring(t, opts = {}) {
244
+ const { tension = 200, friction = 20 } = opts;
245
+ const w = Math.sqrt(tension);
246
+ const zeta = friction / (2 * w);
247
+ if (zeta >= 1) return 1 - Math.exp(-friction * t / 2) * (1 + friction * t / 2);
248
+ const wd = w * Math.sqrt(1 - zeta * zeta);
249
+ return 1 - Math.exp(-zeta * w * t) * (Math.cos(wd * t) + (zeta * w / wd) * Math.sin(wd * t));
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Character Emotion System
256
+
257
+ 31 emotions grouped by family:
258
+
259
+ | Family | Emotions |
260
+ |--------|----------|
261
+ | **Joy** | happy, excited, laugh, love, proud, starstruck, playful, hopeful, relieved |
262
+ | **Sly** | smug, cool, mischief, suspicious |
263
+ | **Low** | sad, cry, bored, sleepy, ko |
264
+ | **Hot** | angry, furious, determined, disgusted |
265
+ | **Alarm** | scared, nervous, surprised, confused, dizzy |
266
+ | **Mind** | neutral, thinking, idea, shy |
267
+
268
+ **Emotion blending** — never snap between emotions, always blend through anticipation:
269
+
270
+ ```javascript
271
+ // emotions(t, keys) — acted emotion changes
272
+ // Around each key: squints just before, swaps face, fires a take
273
+ function emotions(t, keys) {
274
+ // Find current segment
275
+ for (let i = keys.length - 1; i >= 0; i--) {
276
+ const [keyT, name, overrides = {}] = keys[i];
277
+ if (t >= keyT) {
278
+ const nextKeyT = i < keys.length - 1 ? keys[i+1][0] : Infinity;
279
+ const lt = t - keyT;
280
+ const segDur = nextKeyT - keyT;
281
+
282
+ // Anticipation squint just before transition (0.12s before)
283
+ const anticipation = i < keys.length - 1
284
+ ? Math.max(0, (t - (nextKeyT - 0.12)) / 0.12)
285
+ : 0;
286
+
287
+ return {
288
+ emotion: name,
289
+ squint: anticipation * 0.8,
290
+ take: anticipation > 0.5 ? (anticipation - 0.5) * 2 : 0,
291
+ ...overrides,
292
+ };
293
+ }
294
+ }
295
+ return { emotion: keys[0][1] };
296
+ }
297
+ ```
298
+
299
+ ---
300
+
301
+ ## Review Loop (Render → Look → Fix)
302
+
303
+ You **cannot see motion by reading code**. Render and look at every shot:
304
+
305
+ 1. **Contact sheet**: every shot's first, middle, and last frames (checks the shape of the whole piece)
306
+ 2. **Strip**: every frame of a key motion — turns, takes, jumps, throws, transitions (0.1–0.15 s step)
307
+ 3. **Crop**: full-resolution detail — faces, hands, contacts, glows
308
+
309
+ At each zoom level, check:
310
+ - **Read**: is the event clear from the sheet alone? Is the character big enough? Does it separate from the background?
311
+ - **Timing**: does every read have enough time to land?
312
+ - **Motion**: does every move have anticipation and follow-through? Any pops or snaps?
313
+ - **Boil**: in a strip, does stable linework match between frames? Anything still that changes every frame needs its own seed.
314
+ - **Contacts**: do feet touch the ground? Do held things touch the arm tips?
315
+ - **Transitions**: do the first and last 0.5 s of every shot open and close with a transition?
316
+ - **Rules**: any text? Any 3D? Any dead stretch where nothing is happening?
317
+
318
+ **Budget**: at least one sheet per shot, a strip for every key motion and transition, and a crop for every face that carries the story.
319
+
320
+ ---
321
+
322
+ ## Common Failures
323
+
324
+ | Failure | What it looks like |
325
+ |---------|-------------------|
326
+ | Signs / captions | Words anywhere on screen |
327
+ | Dead character | Standing still, smiling, nothing happening |
328
+ | Uniform speed | Everything at one brisk speed, events stacked |
329
+ | Flashed moments | Moments over before viewer understands them |
330
+ | Tiny character | Whole video with character < 12 units in a big empty landscape |
331
+ | Snapping faces | Expression swaps from one frame to the next |
332
+ | Mechanical motion | Linear moves, every part at once, mirrored arms |
333
+ | Timid poses | Takes that barely read |
334
+ | Jittery linework | Still things re-boiling because a moving element shifted the PRNG stream |
335
+ | Hard cuts everywhere | No transitions at seams |
336
+ | 3D rotation | `rotateY`, perspective boxes, or projected turns |
337
+ | Digital shapes | Plain CSS shapes mixed into drawn content |
338
+
339
+ ---
340
+
341
+ ## Render Pipeline
342
+
343
+ ```bash
344
+ # Contact sheet: shape of the whole piece
345
+ node render.mjs --sheet=0.1,0.8,1.6,2.4,3.1,3.9 --cols=6 --w=320 --out=out/check/sheet.jpg
346
+
347
+ # Strip: every frame of a moment (turns, takes, jumps, throws)
348
+ node render.mjs --strip=2.1:2.6 --cols=6 --w=320 --out=out/check/strip.jpg
349
+
350
+ # Crop: full-resolution detail (face, hands, contacts, glows)
351
+ node render.mjs --sheet=2.3,2.4 --crop=760,420,500,400 --w=500 --out=out/check/face.jpg
352
+
353
+ # Crop following a world point through camera
354
+ node render.mjs --strip=2.1:2.6 --crop-at=960,700,500,400 --out=out/check/feet.jpg
355
+
356
+ # Final render
357
+ node render.mjs --clip --out=out/video.mp4
358
+ ```
359
+
360
+ FrameForge equivalent:
361
+ ```
362
+ render_animation({ mode: "review", contactSheet: true, stripStart: 2.1, stripEnd: 2.6 })
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Linux / Headless Rendering
368
+
369
+ For cloud or CI rendering without a GPU:
370
+
371
+ ```bash
372
+ # Ubuntu 23.10+ blocks Chrome's sandbox
373
+ node render.mjs --clip --out=out/video.mp4 # adds --no-sandbox automatically on Linux
374
+
375
+ # Software WebGL (SwiftShader) when no GPU
376
+ node render.mjs --soft-gl --clip --out=out/video.mp4
377
+
378
+ # NVIDIA GPU on headless Linux
379
+ node render.mjs --gpu-angle=gl-egl --clip --out=out/video.mp4
380
+ # or: --gpu-angle=vulkan
381
+
382
+ # Check which renderer each flag set gets
383
+ node gpu_probe.mjs <chrome-path>
384
+
385
+ # Playwright's Chromium is found automatically
386
+ ```
387
+
388
+ FrameForge Playwright renderer handles this automatically via `--no-sandbox` on Linux.
@@ -0,0 +1,41 @@
1
+ # Editorial Paper Collage & Halftone B-Roll
2
+
3
+ Derived from `pyang5166/gbro-collage-broll`. Converts abstract script claims and voiceover segments into premium editorial paper-collage animations.
4
+
5
+ ---
6
+
7
+ ## The Three-Gate Validation Process
8
+
9
+ Never generate collage visual components blindly. Follow the 3-gate rule:
10
+
11
+ 1. **Gate 1: Semantic Claim Extraction**
12
+ - Identify the primary noun and verb in the voiceover beat.
13
+ - Example: *"Interest rates surged to 20-year highs"* $\to$ Noun: `Central Bank / Rates`, Verb: `Surge / Escalator`.
14
+
15
+ 2. **Gate 2: Visual Metaphor Alignment**
16
+ - Match abstract concepts to tactile paper cutouts:
17
+ - Growth / Inflation $\to$ Stacking banknotes, inflating balloon stamp, ascending staircase.
18
+ - Tech / AI $\to$ Vintage computer monitor, mechanical gear cutouts, circuit blueprint overlay.
19
+ - Time $\to$ Torn wall calendar pages, pocket watch face, pendulum swing.
20
+
21
+ 3. **Gate 3: Spatial Layout & Halftone Verification**
22
+ - 2.5D perspective tilt (`rotateX(15deg) rotateY(-10deg)`).
23
+ - Halftone dot texture overlay (`radial-gradient(circle, #000 1.5px, transparent 1.5px)`).
24
+ - Torn paper drop shadow (`filter: drop-shadow(4px 6px 0px rgba(0,0,0,0.35))`).
25
+
26
+ ---
27
+
28
+ ## 12fps Step-Stagger Entrance Contract
29
+
30
+ Collage elements must feel hand-placed, not mechanically smooth:
31
+
32
+ ```javascript
33
+ // Step to 12fps stop-motion cadence
34
+ const step = Math.floor(t * 12);
35
+ const dropProgress = Math.min(1, step / 6); // settles in 0.5s
36
+
37
+ // Damped paper bounce
38
+ const scale = 1.15 - 0.15 * dropProgress;
39
+ const rot = -4 + Math.sin(seed * 11) * 3;
40
+ ```
41
+
@@ -0,0 +1,217 @@
1
+ # Design Reference — Layouts, Safe Zones, Type Scale, Pacing
2
+
3
+ Production design rules for FrameForge HTML compositor projects.
4
+ These rules apply to every frame whether it is a motion graphic, explainer, reel, or short.
5
+
6
+ ---
7
+
8
+ ## Canvas Formats
9
+
10
+ | Format | Dimensions | Use case |
11
+ |--------|-----------|---------|
12
+ | Landscape | 1920 × 1080 | YouTube, presentations, explainers |
13
+ | Portrait | 1080 × 1920 | TikTok, Instagram Reels, YouTube Shorts |
14
+ | Square | 1080 × 1080 | Instagram feed (export crop only) |
15
+
16
+ Set in `reel()`: `reel({ width: 1080, height: 1920, fps: 30, duration: 30 })`.
17
+
18
+ ---
19
+
20
+ ## Safe Zones
21
+
22
+ **Always keep story-critical content inside the safe zone.** Platform overlays (captions, UI chrome, handle badges) encroach from all edges.
23
+
24
+ ### Landscape (1920 × 1080)
25
+
26
+ ```
27
+ ┌────────────────────────────────────────────────────────┐
28
+ │ 80px 80px │ ← bleed margin
29
+ │ ┌──────────────────────────────────────────────────┐ │
30
+ │ │ │ │
31
+ │ │ TEXT & FOCAL CONTENT SAFE ZONE │ │ 96px top
32
+ │ │ 1728 × 888 px (centred) │ │
33
+ │ │ │ │
34
+ │ └──────────────────────────────────────────────────┘ │
35
+ │ 80px 120px │ ← bottom caption strip
36
+ └────────────────────────────────────────────────────────┘
37
+ ```
38
+
39
+ - **Title / headline text:** keep within x: 96–1824, y: 96–200
40
+ - **Body / callout text:** keep within x: 96–1824, y: 200–880
41
+ - **Caption strip:** y: 880–980 (reserved for `captions()` output)
42
+ - **Never place story-critical text below y: 880** (platform UIs cover it)
43
+
44
+ ### Portrait (1080 × 1920)
45
+
46
+ ```
47
+ Top safe: y > 120 (avoid top bar + handle overlay)
48
+ Bottom safe: y < 1680 (avoid TikTok/Reels caption strip)
49
+ Side safe: x: 60–1020
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Five-Layer Depth Stack
55
+
56
+ Every scene must maintain clear atmospheric depth. Name layers accordingly.
57
+
58
+ | Z-index | Layer name | Contents |
59
+ |---------|-----------|---------|
60
+ | 0 | `bg` | Radial gradient, sky, atmospheric tint |
61
+ | 1 | `env` | Grid lines, floor plane, distant elements, star field |
62
+ | 2 | `subject` | Primary visual entity (character, diagram, device) |
63
+ | 3 | `overlay` | Callout lines, pulse packets, data labels, badges |
64
+ | 4 | `fg` | Vignette, glass arcs, foreground foliage, grain |
65
+
66
+ In CSS: use `z-index: 0..4` on stage children or set `position: absolute` stacking order.
67
+
68
+ ---
69
+
70
+ ## Type Scale
71
+
72
+ ### Landscape
73
+
74
+ | Role | Size | Weight | Font family |
75
+ |------|------|--------|-------------|
76
+ | Display / hero | 96–120px | 800 | Archivo, Rajdhani, or Cinzel |
77
+ | Headline | 64–80px | 700 | Archivo, Inter |
78
+ | Subhead | 40–52px | 600 | Inter, Rajdhani |
79
+ | Body | 28–36px | 400 | Inter, system-ui |
80
+ | Caption / badge | 20–24px | 500 | Inter |
81
+ | Fine print | 16–18px | 400 | Inter |
82
+
83
+ ### Portrait (scale down ~25%)
84
+
85
+ | Role | Size |
86
+ |------|------|
87
+ | Display | 72–88px |
88
+ | Headline | 48–60px |
89
+ | Body | 22–28px |
90
+ | Caption | 18–20px |
91
+
92
+ ### Regional stacks
93
+
94
+ | Script | Primary | Fallback |
95
+ |--------|---------|---------|
96
+ | Malayalam | Manjari, Gayathri, Chilanka | Nirmala UI, Noto Sans Malayalam |
97
+ | Devanagari (Hindi) | Noto Sans Devanagari | Nirmala UI |
98
+ | Tamil | Noto Sans Tamil | Latha |
99
+ | Telugu | Noto Sans Telugu | Gautami |
100
+ | CJK (Chinese/Japanese/Korean) | Noto Sans CJK SC/JP/KR | system-ui |
101
+ | Arabic | Noto Sans Arabic | Tahoma |
102
+ | Latin | Inter, Archivo | system-ui, sans-serif |
103
+
104
+ **WCAG AAA minimum contrast:** 7:1 text:background. Check with `seg()` + luminance math at every frame where background colour changes.
105
+
106
+ ---
107
+
108
+ ## Pacing Rules (cuts per second)
109
+
110
+ | Format | Min shot length | Max shot length | Pacing feel |
111
+ |--------|----------------|----------------|------------|
112
+ | Short-form reel | 1.5 s | 5 s | Fast, punchy |
113
+ | Educational explainer | 3 s | 12 s | Measured |
114
+ | Cinematic | 4 s | 20 s | Deliberate |
115
+
116
+ **Beats per minute → cuts per minute:** For music-synced videos, place cut points on downbeats. At 120 BPM, that's every 2 seconds (or every 4 for half-time feel).
117
+
118
+ ---
119
+
120
+ ## Motion Budget per Shot
121
+
122
+ Keep total simultaneous animated properties per shot under these thresholds to avoid visual noise.
123
+
124
+ | Properties moving at once | Perception |
125
+ |--------------------------|-----------|
126
+ | 1–2 | Clean, focused |
127
+ | 3–4 | Dynamic, energetic |
128
+ | 5–6 | Busy — only justified for reveal moments |
129
+ | 7+ | Chaotic — avoid |
130
+
131
+ ---
132
+
133
+ ## Transition Catalogue
134
+
135
+ Named transitions for `STORYBOARD.md` `transition_in` / `transition_out`:
136
+
137
+ | Name | Description | Duration |
138
+ |------|-------------|---------|
139
+ | `cut` | Instant switch | 0 s |
140
+ | `fade-from-black` | Fade in from black | 0.5–0.8 s |
141
+ | `fade-to-black` | Fade out to black | 0.4–0.6 s |
142
+ | `dissolve` | Alpha crossfade | 0.3–0.5 s |
143
+ | `wipe-left` | Wipe from right to left | 0.4 s |
144
+ | `wipe-right` | Wipe from left to right | 0.4 s |
145
+ | `iris-in` | Circle grows from centre | 0.6 s |
146
+ | `iris-out` | Circle shrinks to point | 0.5 s |
147
+ | `cross-dissolve` | Overlap dissolve | 0.4 s |
148
+ | `push-left` | New slide pushes old left | 0.5 s |
149
+ | `push-right` | New slide pushes old right | 0.5 s |
150
+ | `flash` | Single white frame | 1 frame |
151
+ | `dip-to-white` | Fade to white, then in | 0.6 s |
152
+
153
+ **Rule:** Exits are always faster than entrances (motion craft rule #4).
154
+ For `cut`, no transition layer is needed. For all others, create a `layer('transition-out')` at the end of the outgoing shot and a `layer('transition-in')` at the start of the incoming shot.
155
+
156
+ ---
157
+
158
+ ## Camera Motion Vocabulary
159
+
160
+ | Keyword | Implementation |
161
+ |---------|--------------|
162
+ | `ken_burns` | `scale: 1.0 + p * 0.06` + subtle pan (Lissajous) |
163
+ | `zoom_in` | `scale: 1.0 + p * 0.12`, pan to subject |
164
+ | `zoom_out` | `scale: 1.12 - p * 0.12` |
165
+ | `pan_left` | `translateX: p * -80px` |
166
+ | `pan_right` | `translateX: p * 80px` |
167
+ | `static` | No transform — for diagram / data shots |
168
+ | `auto` | Pick from above based on `visual_description` |
169
+
170
+ `p = t / REEL.duration` — normalised film progress (0 → 1).
171
+
172
+ Camera never snaps. All motion is a smooth function of `p`. Cuts are handled by resetting `p` to 0 in the local shot frame (`lt`).
173
+
174
+ ---
175
+
176
+ ## Colour Palette Conventions
177
+
178
+ | Role | Recommendation |
179
+ |------|---------------|
180
+ | Background (dark) | `#0c1116` to `#111827` |
181
+ | Background (light) | `#f8f9fa` to `#ffffff` |
182
+ | Primary accent | Saturated, 60–70% lightness: `#3b82f6`, `#f59e0b`, `#10b981` |
183
+ | Text on dark | `#f8fafc` (95% white) — never pure `#ffffff` (too harsh) |
184
+ | Text on light | `#1f2328` (near black) |
185
+ | Muted text | `#57606a` |
186
+ | Warning / alert | `#ef4444` — use sparingly |
187
+
188
+ **One palette rule:** Pick one accent colour per video. Two maximum. Three or more = visual noise.
189
+
190
+ ---
191
+
192
+ ## Continuous Life Checklist
193
+
194
+ Every shot must have at least one of these running at all times (motion craft rule #7):
195
+
196
+ - [ ] Background gradient slowly shifting hue (`kf(t, [[0, 0], [REEL.duration, 10]])` hue offset)
197
+ - [ ] Subtle camera drift (Lissajous pan `sin(0.4*t) * 6px`)
198
+ - [ ] Particle system ticking (positions from `hash(i) * width`, animated by `t`)
199
+ - [ ] Progress indicator animating (ring, bar, counter)
200
+ - [ ] Wave / oscillation on any data element
201
+ - [ ] Character breathing (`sin(3*t) * 4px` vertical drift)
202
+
203
+ A completely frozen background for more than 3 consecutive seconds reads as a PowerPoint slide, not a video.
204
+
205
+ ---
206
+
207
+ ## Closed-Loop QA Thresholds
208
+
209
+ | Metric | Pass | Fail |
210
+ |--------|------|------|
211
+ | Content presence (foreground variance) | > 1.5% | ≤ 1.5% |
212
+ | Overexposure (bright pixel share) | < 92% | ≥ 92% |
213
+ | Compositional coverage (bbox area share) | > 8% | ≤ 8% |
214
+ | Motion delta (t=30% vs t=70%) | pixels differ | identical hash |
215
+ | Overall QA score | ≥ 7.0 / 10 | < 7.0 |
216
+
217
+ Run `node scripts/harness.mjs quality` to check all five. The encode step refuses to proceed if the report is absent or failing.