creator-editing-studio 1.3.5 → 1.3.7

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.
@@ -33,6 +33,14 @@ out/ finished renders.
33
33
  - `npm run ramp -- <clip> --ramp "3-5:1->0.4"` — eased speed ramps
34
34
  - `npm run sync -- <a> <b>` — line up a second angle or a separate mic
35
35
  - `npm run refstyle -- <reference clip>` — measure a reference edit's pacing and colour
36
+ - `npm run refmotion -- <reference clip>` — study HOW a reference moves and sounds: every move's speed-up and
37
+ settle, the holds between moves, every cut, every frame across each transition (strips to look at),
38
+ standout sounds and how far they lead the picture, plus a spectrogram of the sound. Run it on any clip
39
+ the person shares as a reference, then LOOK at the strips and spectrogram before planning — numbers
40
+ alone do not show how one scene turns into the next.
41
+ - `python scripts/track_head.py <clip> <first> <last> <out.json> --check <img>` — motion tracking: where the head
42
+ is, frame by frame (only when asked; see "Motion tracking" below)
43
+ - `python scripts/setup_models.py --language <code>` — (re)install the models for a video language
36
44
  - `npm run stills -- <Comp> --every=45` — many stills at once, with contact sheets
37
45
  - `npm run check` — typecheck plus motion lint. Must pass.
38
46
  - `npm run motion-check -- <Comp> --scale=2` — frame-by-frame POP / CUT / STUTTER detection
@@ -83,6 +91,11 @@ that is when people can actually name what they like.
83
91
  Then transcribe with word-level timings. **The transcript is the truth, not the script and not
84
92
  what they remember saying.**
85
93
 
94
+ The language comes from setup (`studio.config.json`; the transcriber and aligner read it). For Indian
95
+ languages and Hinglish, **ask for the script or captions file first** and match it to the audio
96
+ (`python scripts/align-script.py <media> <script.srt> --whole`) — matching known words beats recognising
97
+ them, especially in code-mixed speech. Recognise only when there is no script.
98
+
86
99
  ### 2. The first cut, before the plan
87
100
 
88
101
  Cut the silence, the filler and the retakes first. This is mechanical — no design decisions in
@@ -116,6 +129,31 @@ what stops motion graphics drifting into generic.
116
129
  Then stop and ask: *here is the plan, change anything that is off, say go ahead and I will cut
117
130
  it.* Wait for a yes. "Go ahead", "yes", "approved", "ship it" is a yes. Silence is not.
118
131
 
132
+ ### 4. The live preview — after the yes, before any render
133
+
134
+ Once they approve, build the edit and **open the live preview yourself** — `preview_edit({video})`. They must
135
+ never have to ask for it, and never get a draft MP4 instead of it. It plays the edit in the chat with instant
136
+ controls (size, position, timing, sound, icon, follow head) that cost no messages; see `preview/README.md` for
137
+ what each video needs (`preview.tsx`, `TweaksProvider`).
138
+
139
+ - A change typed in the preview arrives as a chat message: change only that beat, then call `preview_edit`
140
+ again so the updated preview opens below. **Undo** = `preview_undo`, then `preview_edit`.
141
+ - **Save** and **Render** arrive as one message each, carrying their adjustments as JSON for the video's
142
+ `tweaks.json` — write it exactly.
143
+ - Render only when they press **Render final video**, and give the estimated render time only then.
144
+ - If the preview tool is missing (not switched on yet), say so in one line and fall back to rendering with
145
+ the time estimate. Never send them off to fix it mid-edit.
146
+
147
+ ### Motion tracking — only when asked
148
+
149
+ Text or graphics can ride with the speaker's head. **Never a default** — use it when they ask ("make the title
150
+ follow my head") or press **Follow head** in the preview. Track only the stretch that needs it:
151
+ `python scripts/track_head.py <clip> <first source frame> <last source frame> src/videos/<video>/head-track.json
152
+ --check work/<video>/track-check.jpg` (≈1 minute per 5 seconds), and **look at the check image** — the box must
153
+ sit on the face in every tile — before using it. Then either pin a graphic with `<HeadPin>`
154
+ (`src/components/HeadPin.tsx`) or turn on `follow` for the beat. A wide line will not fit above a head: split
155
+ it. It takes half the head's tilt and trails two frames — attached, not glued.
156
+
119
157
  ### Keep it proportionate
120
158
 
121
159
  The plan is there to save them a rebuild, not to put a form in front of them.
@@ -240,260 +278,260 @@ preserves the damage rather than repairing it. It is clearly audible and it gets
240
278
  every extra render. Use `node scripts/voice.mjs` to pull the voice losslessly from the camera
241
279
  file, render the picture muted, and mux the two together at the end.
242
280
 
243
- ## Non-negotiables
244
-
245
- 1. **No magic numbers.** Durations, curves, springs, staggers, blur come from `src/design/motion.ts`.
246
- Colours, fonts, sizes, spacing come from `src/design/tokens.ts`. If a needed token does not
247
- exist, add it to the token file (with a comment) instead of inlining a value.
248
- 2. **Timing derives from the spine.** Animation start frames come from word timestamps, beats,
249
- or other elements' timings via helpers — never hand-typed frame numbers.
250
- 3. **`npm run check` and `motion-check --scale=2` must pass** before any render is shown to the user.
251
- 4. **Verify visually before claiming done** (see Verification). Never say something "looks good"
252
- without having rendered and viewed the frames.
253
-
281
+ ## Non-negotiables
282
+
283
+ 1. **No magic numbers.** Durations, curves, springs, staggers, blur come from `src/design/motion.ts`.
284
+ Colours, fonts, sizes, spacing come from `src/design/tokens.ts`. If a needed token does not
285
+ exist, add it to the token file (with a comment) instead of inlining a value.
286
+ 2. **Timing derives from the spine.** Animation start frames come from word timestamps, beats,
287
+ or other elements' timings via helpers — never hand-typed frame numbers.
288
+ 3. **`npm run check` and `motion-check --scale=2` must pass** before any render is shown to the user.
289
+ 4. **Verify visually before claiming done** (see Verification). Never say something "looks good"
290
+ without having rendered and viewed the frames.
291
+
254
292
  ---
255
293
 
256
- ## The 12 motion laws
257
-
258
- 1. **No linear easing** — except constant-velocity loops/scrolls (mark with `// motion-ok`).
259
- 2. **Entrances ease-out (`EASE.out`), exits ease-in (`EASE.in`), on-screen A-to-B moves ease-in-out (`EASE.inOut`).**
260
- 3. **Exits are faster than entrances** — `EXIT_RATIO` (0.65). Use `exitFrames()`.
261
- 4. **Max 2–3 animated properties per element.** No fade + scale + rotate + blur together.
262
- 5. **Never opacity alone.** Pair with 8–24px travel or scale >= 0.94. Opacity completes at
263
- `OPACITY_LEAD` (60%) of the transform.
264
- 6. **Never scale from below 0.9.** Text scaling from zero is the #1 AI-edit tell.
265
- 7. **Blur-in capped at 12px**, fully resolved before the transform ends.
266
- 8. **Groups always stagger** (`STAGGER.tight/normal/dramatic`). Never simultaneous.
267
- 9. **Distance couples to duration sub-linearly** (`travelFrames()`), never linearly.
268
- 10. **Anything that moves fast is motion-blurred** (see Smoothness system). No hard edge may jump
269
- across the frame unblurred.
270
- 11. **Readable hold:** text stays fully visible >= `readFrames(text)` (min 0.9s, +55ms/word).
271
- 12. **Continuity:** every transition inherits the outgoing motion (see below).
272
-
273
- ### Continuity (what makes it feel connected, not cut together)
274
-
275
- - **Shared elements never re-enter.** If an element exists across two beats, `morph()` its
276
- position/size/radius between layouts; never exit and re-animate it. (TransitionTest: the
277
- eyebrow dot stays on screen and grows into the next card's icon tile.)
278
- - **Velocity handoff.** An exit toward a direction is answered by an entrance continuing that
279
- direction — `handoff(exitTo)`.
280
- - **Overlap, never gap.** The next beat starts before the previous one settles —
281
- `nextBeatAt(prevSettled)`.
282
- - **One camera over cuts.** Lay scenes out on one canvas and glide a camera between them
283
- (`src/lib/camera.ts`) instead of hard-cutting between unrelated layouts. Content that should
284
- stay put during a move (shared elements) lives in screen space, outside the camera.
285
- - **Nothing is cut off.** Compute exit start times backward from the end so the slowest,
286
- most-staggered exit completes on or before the last frame.
287
- - **Sound glues cuts.** Whooshes start `TRANSITION.sfxLeadMs` before a visual change and tail
288
- `sfxTailMs` after.
289
-
294
+ ## The 12 motion laws
295
+
296
+ 1. **No linear easing** — except constant-velocity loops/scrolls (mark with `// motion-ok`).
297
+ 2. **Entrances ease-out (`EASE.out`), exits ease-in (`EASE.in`), on-screen A-to-B moves ease-in-out (`EASE.inOut`).**
298
+ 3. **Exits are faster than entrances** — `EXIT_RATIO` (0.65). Use `exitFrames()`.
299
+ 4. **Max 2–3 animated properties per element.** No fade + scale + rotate + blur together.
300
+ 5. **Never opacity alone.** Pair with 8–24px travel or scale >= 0.94. Opacity completes at
301
+ `OPACITY_LEAD` (60%) of the transform.
302
+ 6. **Never scale from below 0.9.** Text scaling from zero is the #1 AI-edit tell.
303
+ 7. **Blur-in capped at 12px**, fully resolved before the transform ends.
304
+ 8. **Groups always stagger** (`STAGGER.tight/normal/dramatic`). Never simultaneous.
305
+ 9. **Distance couples to duration sub-linearly** (`travelFrames()`), never linearly.
306
+ 10. **Anything that moves fast is motion-blurred** (see Smoothness system). No hard edge may jump
307
+ across the frame unblurred.
308
+ 11. **Readable hold:** text stays fully visible >= `readFrames(text)` (min 0.9s, +55ms/word).
309
+ 12. **Continuity:** every transition inherits the outgoing motion (see below).
310
+
311
+ ### Continuity (what makes it feel connected, not cut together)
312
+
313
+ - **Shared elements never re-enter.** If an element exists across two beats, `morph()` its
314
+ position/size/radius between layouts; never exit and re-animate it. (TransitionTest: the
315
+ eyebrow dot stays on screen and grows into the next card's icon tile.)
316
+ - **Velocity handoff.** An exit toward a direction is answered by an entrance continuing that
317
+ direction — `handoff(exitTo)`.
318
+ - **Overlap, never gap.** The next beat starts before the previous one settles —
319
+ `nextBeatAt(prevSettled)`.
320
+ - **One camera over cuts.** Lay scenes out on one canvas and glide a camera between them
321
+ (`src/lib/camera.ts`) instead of hard-cutting between unrelated layouts. Content that should
322
+ stay put during a move (shared elements) lives in screen space, outside the camera.
323
+ - **Nothing is cut off.** Compute exit start times backward from the end so the slowest,
324
+ most-staggered exit completes on or before the last frame.
325
+ - **Sound glues cuts.** Whooshes start `TRANSITION.sfxLeadMs` before a visual change and tail
326
+ `sfxTailMs` after.
327
+
290
328
  ---
291
329
 
292
- ## Smoothness system (every one of these was a real defect found by motion-check)
293
-
294
- - **Use the components, not raw transforms.** `<Presence>`, `<MaskReveal>`, `<HighlightMark>`,
295
- `<RollingNumber>` add velocity motion blur automatically. Anything else that moves wraps its
296
- moving element in `<VelocityBlur vx vy>` with `perFrame()` velocities.
297
- - **Velocity motion blur** (`VelocityBlur`): directional blur sized to this frame's movement with
298
- a 180-degree shutter; fades to zero as the element settles. Near-free to render.
299
- - **Wipes** use `wipeMask(p, perFrame(...))`: the leading edge softens with speed. Never wipe with
300
- a hard `clipPath` edge.
301
- - **Camera moves**: wrap the camera canvas in a full-frame `VelocityBlur` with `bleed={0}` and
302
- `overflow: hidden`, velocity from `cameraShot` (see TransitionTest).
303
- - **Zooms / rotations** (not translations): use `<MotionBlur active>` (temporal sampling, centred
304
- on the frame so toggling it never shifts timing). It costs 10x render time while active —
305
- enable only during the move. Never use `@remotion/motion-blur`'s `CameraMotionBlur`: it samples
306
- ahead of the frame, so switching it on/off causes a timing hitch.
307
- - **Counters roll, never tick.** Use `<RollingNumber>`. A counting number changes glyphs in single
308
- frames as it slows, which motion-check flags as pops.
309
- - **Never `will-change`.** It makes each parallel render tab cache text at a different sub-pixel
310
- position, so settled elements flicker between 4 versions. (Lint enforces this.)
311
- - **Screen recordings used as b-roll usually scroll in steps** (they judder). Use a still frame with a
312
- smooth `ScreenView` pan instead, and blur names on the still.
313
- - **Presence `dur` sets entrance AND exit length.** `dur="instant"` makes exits last 3 frames (a snap) —
314
- for containers that only fade in, use at least `"base"`.
315
- - **Final renders are supersampled at `--scale=2`**, then downscaled to 1080x1920. At 1x, Chrome
316
- snaps text to whole pixels, so the slow tail of every ease-out steps ("move 1px, hold, move 1px")
317
- and motion-check reports stutters. Run motion-check with the same `--scale`.
318
- - **Frame rate:** match the footage. If footage is 60fps, build at 60 — per-frame jumps halve.
319
- All tokens are in ms, so compositions work at either rate.
320
-
330
+ ## Smoothness system (every one of these was a real defect found by motion-check)
331
+
332
+ - **Use the components, not raw transforms.** `<Presence>`, `<MaskReveal>`, `<HighlightMark>`,
333
+ `<RollingNumber>` add velocity motion blur automatically. Anything else that moves wraps its
334
+ moving element in `<VelocityBlur vx vy>` with `perFrame()` velocities.
335
+ - **Velocity motion blur** (`VelocityBlur`): directional blur sized to this frame's movement with
336
+ a 180-degree shutter; fades to zero as the element settles. Near-free to render.
337
+ - **Wipes** use `wipeMask(p, perFrame(...))`: the leading edge softens with speed. Never wipe with
338
+ a hard `clipPath` edge.
339
+ - **Camera moves**: wrap the camera canvas in a full-frame `VelocityBlur` with `bleed={0}` and
340
+ `overflow: hidden`, velocity from `cameraShot` (see TransitionTest).
341
+ - **Zooms / rotations** (not translations): use `<MotionBlur active>` (temporal sampling, centred
342
+ on the frame so toggling it never shifts timing). It costs 10x render time while active —
343
+ enable only during the move. Never use `@remotion/motion-blur`'s `CameraMotionBlur`: it samples
344
+ ahead of the frame, so switching it on/off causes a timing hitch.
345
+ - **Counters roll, never tick.** Use `<RollingNumber>`. A counting number changes glyphs in single
346
+ frames as it slows, which motion-check flags as pops.
347
+ - **Never `will-change`.** It makes each parallel render tab cache text at a different sub-pixel
348
+ position, so settled elements flicker between 4 versions. (Lint enforces this.)
349
+ - **Screen recordings used as b-roll usually scroll in steps** (they judder). Use a still frame with a
350
+ smooth `ScreenView` pan instead, and blur names on the still.
351
+ - **Presence `dur` sets entrance AND exit length.** `dur="instant"` makes exits last 3 frames (a snap) —
352
+ for containers that only fade in, use at least `"base"`.
353
+ - **Final renders are supersampled at `--scale=2`**, then downscaled to 1080x1920. At 1x, Chrome
354
+ snaps text to whole pixels, so the slow tail of every ease-out steps ("move 1px, hold, move 1px")
355
+ and motion-check reports stutters. Run motion-check with the same `--scale`.
356
+ - **Frame rate:** match the footage. If footage is 60fps, build at 60 — per-frame jumps halve.
357
+ All tokens are in ms, so compositions work at either rate.
358
+
321
359
  ---
322
360
 
323
- ## Design rules
324
-
325
- **Read `STYLE.md` before any design decision.** It is the house style (type scale, layout, motion,
326
- sound, approved and rejected beats) and wins over anything below or in a reference. Update it
327
- whenever the user approves or rejects something.
328
-
329
- - Everything visual comes from `src/design/tokens.ts`, which is yours to define.
330
- - **Brand essentials:** your display typeface only, at every weight (headlines/stats 800). One accent: accent
331
- `your accent` on ink and cream/white. No blue, no purple, no serif. Signature emphasis is the accent
332
- marker behind the key phrase — use `<HighlightMark>`; don't invent other emphasis styles.
333
- Headlines are short two-part fragments with the payoff highlighted. Numbers are shown raw and
334
- bold ("$1.3M", "11x") and roll in. Eyebrows are uppercase pills with a accent-ringed dot.
335
- Radii are generous, shadows soft, buttons/badges pills.
336
- - Secondary text uses `COLOR.fgSecondary`; `COLOR.fgMuted` fails contrast on cream — decoration only.
337
- - **User style decisions (first project):**
338
- - Captions are **crisp white** (`CAPTION` token, tight + soft dark shadow). Never ink text with a light glow.
339
- Over the cream background white is invisible, so in card layouts captions go **inside the video
340
- card's bottom edge** on the card's dark scrim (`StageState.scrim`).
341
- - Big headline text is **accent with a glow** (`HeroBlock` / `HeroTag`, `heroGlow()`).
342
- - Pointer is a **solid accent arrow** like the reference (`Cursor`), ~68px.
343
- - Brand logos are **real full-colour logos** (`LogoIcon`, SVG Logos CC0 via `src/design/logos.json` —
344
- extract only the icons used). Never approximate a logo that isn't in the set; use a neutral
345
- icon and ask for the official file (Klaviyo is missing).
346
- - A corner tag (e.g. "11 MINUTES" pill) is removed when the video returns full screen.
347
- - **Text behind the subject must stay readable:** the head covers only the lower ~20–30% of the
348
- headline. Head height changes shot to shot (leaning forward lifts it ~80px) — check a still of
349
- EVERY hero moment and raise the block (`top`; put labels above as `eyebrow`) where needed.
350
- - Keep accent highlights away from the accent background glow so they don't disappear into it.
351
- - Stats use proportional figures, not tabular (tabular makes "11" look gappy in your display typeface).
352
- - **Web design systems do not map 1:1 to video.** Keep colour, font families/weights, type-scale
353
- ratios, radii, spacing rhythm, iconography and imagery style — but rescale sizes for a
354
- 1080-wide frame viewed on a phone (body roughly 40–48px, not 16px). Web UI components mostly
355
- do not apply.
356
- - Respect safe zones: `LAYOUT.safeTop` / `LAYOUT.safeBottom` for Reels/Shorts UI.
357
- - Text contrast >= 4.5:1 against whatever is behind it, including over footage.
358
- - Draw graphics in code (SVG/CSS) so every part can animate. Use flat images only for things
359
- that must be real (logos, screenshots, photos). Avoid AI-generated imagery for anything the
360
- viewer is meant to look at.
361
- - Icons: SVG drawn in code or an installed icon library, never image files.
362
-
361
+ ## Design rules
362
+
363
+ **Read `STYLE.md` before any design decision.** It is the house style (type scale, layout, motion,
364
+ sound, approved and rejected beats) and wins over anything below or in a reference. Update it
365
+ whenever the user approves or rejects something.
366
+
367
+ - Everything visual comes from `src/design/tokens.ts`, which is yours to define.
368
+ - **Brand essentials:** your display typeface only, at every weight (headlines/stats 800). One accent: accent
369
+ `your accent` on ink and cream/white. No blue, no purple, no serif. Signature emphasis is the accent
370
+ marker behind the key phrase — use `<HighlightMark>`; don't invent other emphasis styles.
371
+ Headlines are short two-part fragments with the payoff highlighted. Numbers are shown raw and
372
+ bold ("$1.3M", "11x") and roll in. Eyebrows are uppercase pills with a accent-ringed dot.
373
+ Radii are generous, shadows soft, buttons/badges pills.
374
+ - Secondary text uses `COLOR.fgSecondary`; `COLOR.fgMuted` fails contrast on cream — decoration only.
375
+ - **User style decisions (first project):**
376
+ - Captions are **crisp white** (`CAPTION` token, tight + soft dark shadow). Never ink text with a light glow.
377
+ Over the cream background white is invisible, so in card layouts captions go **inside the video
378
+ card's bottom edge** on the card's dark scrim (`StageState.scrim`).
379
+ - Big headline text is **accent with a glow** (`HeroBlock` / `HeroTag`, `heroGlow()`).
380
+ - Pointer is a **solid accent arrow** like the reference (`Cursor`), ~68px.
381
+ - Brand logos are **real full-colour logos** (`LogoIcon`, SVG Logos CC0 via `src/design/logos.json` —
382
+ extract only the icons used). Never approximate a logo that isn't in the set; use a neutral
383
+ icon and ask for the official file (Klaviyo is missing).
384
+ - A corner tag (e.g. "11 MINUTES" pill) is removed when the video returns full screen.
385
+ - **Text behind the subject must stay readable:** the head covers only the lower ~20–30% of the
386
+ headline. Head height changes shot to shot (leaning forward lifts it ~80px) — check a still of
387
+ EVERY hero moment and raise the block (`top`; put labels above as `eyebrow`) where needed.
388
+ - Keep accent highlights away from the accent background glow so they don't disappear into it.
389
+ - Stats use proportional figures, not tabular (tabular makes "11" look gappy in your display typeface).
390
+ - **Web design systems do not map 1:1 to video.** Keep colour, font families/weights, type-scale
391
+ ratios, radii, spacing rhythm, iconography and imagery style — but rescale sizes for a
392
+ 1080-wide frame viewed on a phone (body roughly 40–48px, not 16px). Web UI components mostly
393
+ do not apply.
394
+ - Respect safe zones: `LAYOUT.safeTop` / `LAYOUT.safeBottom` for Reels/Shorts UI.
395
+ - Text contrast >= 4.5:1 against whatever is behind it, including over footage.
396
+ - Draw graphics in code (SVG/CSS) so every part can animate. Use flat images only for things
397
+ that must be real (logos, screenshots, photos). Avoid AI-generated imagery for anything the
398
+ viewer is meant to look at.
399
+ - Icons: SVG drawn in code or an installed icon library, never image files.
400
+
363
401
  ---
364
402
 
365
- ## Compositing: text behind the subject
366
-
367
- Layer order, bottom to top:
368
-
369
- ```
370
- background plate (designed, or the original footage)
371
- text / graphics <- 1–3px blur to sit on the background's focal plane
372
- subject cutout <- same clip, frame-aligned
373
- light wrap <- blurred background screened onto the subject's inner edge, 15–30%
374
- grain + grade <- applied over everything so layers share one look
375
- ```
376
-
377
- - **The cutout must come from the same clip as its background plate**, frame-aligned. Never
378
- combine a cutout from one take with footage from another.
379
- - **The matte is built automatically from ordinary footage.** No green screen, and never ask the
380
- user for one — most people have never shot against green and do not know the term. Segment the
381
- subject per frame, then **temporally smooth the alpha**, which is not optional: a per-frame matte
382
- shimmers along the edge, and type sitting against a head is exactly where that shows. Finish with
383
- spill-free cleanup: ~0.5px choke, 0.5–1px feather.
384
- - **Quality is worst where it is most visible:** flyaway hair, motion blur, and a hand crossing in
385
- front of the text. The light wrap below hides a good deal of it. If a shot simply will not hold,
386
- say so and move the text rather than shipping a halo.
387
- - **If the user happens to have a background-free or green version, prefer it** — chroma keying
388
- still beats automatic matting on hair. Use it **only for the alpha** and take the subject's colour
389
- from the original, so no green ever reaches the edges. Verify the pair first: identical duration,
390
- fps, resolution and frame count, subject aligned on the same frame (one frame out makes a ghost).
391
- This is an upgrade for people who already work that way, never a requirement.
392
- - **Supplied cutouts** must have real transparency (ProRes 4444, WebM with alpha, or PNG sequence).
393
- An MP4 cannot hold transparency — if one arrives, stop and tell the user.
394
- - Play transparent video with `<OffthreadVideo transparent src={staticFile(...)} />`.
395
- - Text behind a moving subject gets 0.85–0.95x counter-parallax; static text reads as a sticker.
396
- - Text should animate in while already partly occluded, not appear fully and then get covered.
397
- - Pull text colour toward the footage's black/white points; pure white over graded footage looks
398
- pasted on.
399
-
403
+ ## Compositing: text behind the subject
404
+
405
+ Layer order, bottom to top:
406
+
407
+ ```
408
+ background plate (designed, or the original footage)
409
+ text / graphics <- 1–3px blur to sit on the background's focal plane
410
+ subject cutout <- same clip, frame-aligned
411
+ light wrap <- blurred background screened onto the subject's inner edge, 15–30%
412
+ grain + grade <- applied over everything so layers share one look
413
+ ```
414
+
415
+ - **The cutout must come from the same clip as its background plate**, frame-aligned. Never
416
+ combine a cutout from one take with footage from another.
417
+ - **The matte is built automatically from ordinary footage.** No green screen, and never ask the
418
+ user for one — most people have never shot against green and do not know the term. Segment the
419
+ subject per frame, then **temporally smooth the alpha**, which is not optional: a per-frame matte
420
+ shimmers along the edge, and type sitting against a head is exactly where that shows. Finish with
421
+ spill-free cleanup: ~0.5px choke, 0.5–1px feather.
422
+ - **Quality is worst where it is most visible:** flyaway hair, motion blur, and a hand crossing in
423
+ front of the text. The light wrap below hides a good deal of it. If a shot simply will not hold,
424
+ say so and move the text rather than shipping a halo.
425
+ - **If the user happens to have a background-free or green version, prefer it** — chroma keying
426
+ still beats automatic matting on hair. Use it **only for the alpha** and take the subject's colour
427
+ from the original, so no green ever reaches the edges. Verify the pair first: identical duration,
428
+ fps, resolution and frame count, subject aligned on the same frame (one frame out makes a ghost).
429
+ This is an upgrade for people who already work that way, never a requirement.
430
+ - **Supplied cutouts** must have real transparency (ProRes 4444, WebM with alpha, or PNG sequence).
431
+ An MP4 cannot hold transparency — if one arrives, stop and tell the user.
432
+ - Play transparent video with `<OffthreadVideo transparent src={staticFile(...)} />`.
433
+ - Text behind a moving subject gets 0.85–0.95x counter-parallax; static text reads as a sticker.
434
+ - Text should animate in while already partly occluded, not appear fully and then get covered.
435
+ - Pull text colour toward the footage's black/white points; pure white over graded footage looks
436
+ pasted on.
437
+
400
438
  ---
401
439
 
402
- ## Per-video pipeline
403
-
404
- 1. **Ingest:** probe every clip (fps, resolution, colour, alpha).
405
- - **Phone footage is usually variable frame rate (VFR).** Conform it to constant frame rate
406
- with ffmpeg before use, or it stutters and drifts out of sync in Remotion.
407
- - Conform everything to one fps. Never mix frame rates.
408
- 2. **Spine:** word-level timings + music beats -> `work/<video>/spine.json`.
409
- Cut pauses/filler from word gaps. All animation timing reads from the spine.
410
- - If the user supplies a script or SRT, it is the **text truth** (spelling, names, brand words):
411
- force-align that exact text to the audio with WhisperX's aligner. Never use SRT timings
412
- directly — they are line-level and padded for readability, not word-accurate.
413
- - With no script, transcribe with WhisperX, then show the transcript for correction before building.
414
- 3. **Cutouts:** build the matte from the footage automatically, or validate a supplied cutout (see above). Only needed for shots that want text behind the subject.
415
- 4. **Beat sheet:** from the script and the user's marked punch lines, write a short plan — which
416
- moment gets which treatment — and confirm with the user before building.
417
- - **Read their saved references first** (`editing_os_references`). They are the whole reason
418
- the edit should look like theirs rather than like the defaults, and a plan written without
419
- them is a plan written for anybody. Say which reference each decision came from, so the
420
- user can see their own taste being applied and correct it when it is being misread.
421
- - Where references are silent, the defaults in STYLE.md apply. Where references and defaults
422
- disagree, the reference wins — it is a real preference, and the default is only a sensible
423
- starting point.
424
- 5. **Build** in `src/compositions/<Video>.tsx` from components + helpers + tokens only.
425
- 6. **Verify** (below), then show the user.
426
- 7. **Sound:** the user adds SFX and music themselves. Deliver a cue list (timestamps of every
427
- transition and hit, with suggested whoosh start = `TRANSITION.sfxLeadMs` before the change) so
428
- they can place sounds quickly. Render without music unless asked.
429
- 8. **Render** at `--scale=2`, downscaled to 1080x1920 unless told otherwise.
430
-
440
+ ## Per-video pipeline
441
+
442
+ 1. **Ingest:** probe every clip (fps, resolution, colour, alpha).
443
+ - **Phone footage is usually variable frame rate (VFR).** Conform it to constant frame rate
444
+ with ffmpeg before use, or it stutters and drifts out of sync in Remotion.
445
+ - Conform everything to one fps. Never mix frame rates.
446
+ 2. **Spine:** word-level timings + music beats -> `work/<video>/spine.json`.
447
+ Cut pauses/filler from word gaps. All animation timing reads from the spine.
448
+ - If the user supplies a script or SRT, it is the **text truth** (spelling, names, brand words):
449
+ force-align that exact text to the audio with WhisperX's aligner. Never use SRT timings
450
+ directly — they are line-level and padded for readability, not word-accurate.
451
+ - With no script, transcribe with WhisperX, then show the transcript for correction before building.
452
+ 3. **Cutouts:** build the matte from the footage automatically, or validate a supplied cutout (see above). Only needed for shots that want text behind the subject.
453
+ 4. **Beat sheet:** from the script and the user's marked punch lines, write a short plan — which
454
+ moment gets which treatment — and confirm with the user before building.
455
+ - **Read their saved references first** (`editing_os_references`). They are the whole reason
456
+ the edit should look like theirs rather than like the defaults, and a plan written without
457
+ them is a plan written for anybody. Say which reference each decision came from, so the
458
+ user can see their own taste being applied and correct it when it is being misread.
459
+ - Where references are silent, the defaults in STYLE.md apply. Where references and defaults
460
+ disagree, the reference wins — it is a real preference, and the default is only a sensible
461
+ starting point.
462
+ 5. **Build** in `src/compositions/<Video>.tsx` from components + helpers + tokens only.
463
+ 6. **Verify** (below), then show the user.
464
+ 7. **Sound:** the user adds SFX and music themselves. Deliver a cue list (timestamps of every
465
+ transition and hit, with suggested whoosh start = `TRANSITION.sfxLeadMs` before the change) so
466
+ they can place sounds quickly. Render without music unless asked.
467
+ 8. **Render** at `--scale=2`, downscaled to 1080x1920 unless told otherwise.
468
+
431
469
  ---
432
470
 
433
- ## Audio standard (audio is half the reel — the user will not accept a bad mix)
434
-
435
- What went wrong on an early video: the voice itself was bit-for-bit intact (measured against the source),
436
- but 2.3–3.5s whoosh files and a 2.4s riser kept playing UNDER the speech, which made the voice sound
437
- wrong. Claude cannot hear, so the mix must be engineered and measured, never guessed.
438
-
439
- 1. **The voice is never touched.** Render the picture `--muted`. The voice in every deliverable is the
440
- original recording's audio (stream-copied, or at most one encode), plus only a single gain change.
441
- No limiter, no dynamic normaliser (ffmpeg `loudnorm` single-pass pumps), no repeated AAC encodes.
442
- 2. **Sound effects are short and placed in gaps.** Trim every SFX to what the motion needs (whoosh
443
- 0.4–0.8s, hits 0.3–0.6s) with a 60–120ms fade-out. Nothing longer than ~0.8s may sit under a spoken
444
- word unless it is at least 24 dB under the voice. Risers go in pauses, not under speech.
445
- 3. **Duck SFX under the voice**: mix with ffmpeg `sidechaincompress` keyed from the voice (SFX drop
446
- ~6 dB while a word is spoken), and high-pass whooshes/impacts around 120–150 Hz so they don't muddy it.
447
- 4. **Measure before delivering** (the window scan in the project folder used `f32le` PCM from ffmpeg):
448
- voice-vs-mix difference per 0.5s window — any window where SFX energy is within 12 dB of the
449
- voice while a word is spoken gets fixed. Final: -14 LUFS integrated, true peak <= -1 dBTP, one
450
- AAC encode at 256k.
451
- 5. **Always also deliver stems + a cue sheet**: `voice.wav`, `sfx.wav` (the placed, trimmed SFX bus),
452
- and `SFX-CUES.txt` with numbered, timecoded SFX files — so the user can rebalance in Premiere in
453
- minutes. Picture-only MP4 alongside.
454
- 6. The user picks and judges sounds by ear; Claude says plainly that it cannot hear and asks for
455
- timecoded audio notes on the first draft.
456
-
471
+ ## Audio standard (audio is half the reel — the user will not accept a bad mix)
472
+
473
+ What went wrong on an early video: the voice itself was bit-for-bit intact (measured against the source),
474
+ but 2.3–3.5s whoosh files and a 2.4s riser kept playing UNDER the speech, which made the voice sound
475
+ wrong. Claude cannot hear, so the mix must be engineered and measured, never guessed.
476
+
477
+ 1. **The voice is never touched.** Render the picture `--muted`. The voice in every deliverable is the
478
+ original recording's audio (stream-copied, or at most one encode), plus only a single gain change.
479
+ No limiter, no dynamic normaliser (ffmpeg `loudnorm` single-pass pumps), no repeated AAC encodes.
480
+ 2. **Sound effects are short and placed in gaps.** Trim every SFX to what the motion needs (whoosh
481
+ 0.4–0.8s, hits 0.3–0.6s) with a 60–120ms fade-out. Nothing longer than ~0.8s may sit under a spoken
482
+ word unless it is at least 24 dB under the voice. Risers go in pauses, not under speech.
483
+ 3. **Duck SFX under the voice**: mix with ffmpeg `sidechaincompress` keyed from the voice (SFX drop
484
+ ~6 dB while a word is spoken), and high-pass whooshes/impacts around 120–150 Hz so they don't muddy it.
485
+ 4. **Measure before delivering** (the window scan in the project folder used `f32le` PCM from ffmpeg):
486
+ voice-vs-mix difference per 0.5s window — any window where SFX energy is within 12 dB of the
487
+ voice while a word is spoken gets fixed. Final: -14 LUFS integrated, true peak <= -1 dBTP, one
488
+ AAC encode at 256k.
489
+ 5. **Always also deliver stems + a cue sheet**: `voice.wav`, `sfx.wav` (the placed, trimmed SFX bus),
490
+ and `SFX-CUES.txt` with numbered, timecoded SFX files — so the user can rebalance in Premiere in
491
+ minutes. Picture-only MP4 alongside.
492
+ 6. The user picks and judges sounds by ear; Claude says plainly that it cannot hear and asks for
493
+ timecoded audio notes on the first draft.
494
+
457
495
  ---
458
496
 
459
- ## Verification (required before saying anything is done)
460
-
461
- Claude cannot watch video. Quality is checked with frames and maths, and the user makes the
462
- final call at playback speed. Be explicit about that when reporting.
463
-
464
- 1. `npm run check` passes.
465
- 2. `npm run motion-check -- <Comp> --scale=2` reports **no POP, CUT or STUTTER**. Allow intentional
466
- cuts with `--allow=<frame>`. For each FAST warning, render that frame and confirm the moving
467
- edge is blurred. Read the motion timeline for dead air and rhythm.
468
- 3. Render stills and **look at them**: first frame, each element mid-entrance, peak-speed frames
469
- (blur visible), fully settled (crisp), mid-exit, and the **last frame** (clean).
470
- 4. Check: no text collides with the subject's face, safe zones respected, contrast holds,
471
- no clipped descenders (g, p, y, commas) in masked text.
472
- 5. Report what was verified and what still needs the user's eyes at full speed.
473
-
497
+ ## Verification (required before saying anything is done)
498
+
499
+ Claude cannot watch video. Quality is checked with frames and maths, and the user makes the
500
+ final call at playback speed. Be explicit about that when reporting.
501
+
502
+ 1. `npm run check` passes.
503
+ 2. `npm run motion-check -- <Comp> --scale=2` reports **no POP, CUT or STUTTER**. Allow intentional
504
+ cuts with `--allow=<frame>`. For each FAST warning, render that frame and confirm the moving
505
+ edge is blurred. Read the motion timeline for dead air and rhythm.
506
+ 3. Render stills and **look at them**: first frame, each element mid-entrance, peak-speed frames
507
+ (blur visible), fully settled (crisp), mid-exit, and the **last frame** (clean).
508
+ 4. Check: no text collides with the subject's face, safe zones respected, contrast holds,
509
+ no clipped descenders (g, p, y, commas) in masked text.
510
+ 5. Report what was verified and what still needs the user's eyes at full speed.
511
+
474
512
  ---
475
513
 
476
- ## Remotion gotchas
477
-
478
- - Never use CSS `transition`/`animation` — drive everything from `useCurrentFrame()`.
479
- - Never use `Math.random()` — use `random(seed)` from `remotion`.
480
- - Never use `will-change` (render-tab flicker, see Smoothness system).
481
- - **`<Presence>` owns its element's `transform` and `opacity`.** Any extra transform (centring with
482
- `translateX(-50%)`) or opacity (fading content) passed in `style` is silently overwritten — put it
483
- on an inner wrapper. This bug appeared twice in the first project (off-centre tag, labels that
484
- never faded).
485
- - em-based spacing (gaps, padding) resolves against the element's own font-size: set `fontSize`
486
- on the container, or word gaps collapse.
487
- - Masked text needs travel > 100% and container padding, or descenders stay visible.
488
- - Load fonts through `@remotion/fonts` (brand your display typeface is local in `assets/fonts/`) so renders wait for them.
489
-
514
+ ## Remotion gotchas
515
+
516
+ - Never use CSS `transition`/`animation` — drive everything from `useCurrentFrame()`.
517
+ - Never use `Math.random()` — use `random(seed)` from `remotion`.
518
+ - Never use `will-change` (render-tab flicker, see Smoothness system).
519
+ - **`<Presence>` owns its element's `transform` and `opacity`.** Any extra transform (centring with
520
+ `translateX(-50%)`) or opacity (fading content) passed in `style` is silently overwritten — put it
521
+ on an inner wrapper. This bug appeared twice in the first project (off-centre tag, labels that
522
+ never faded).
523
+ - em-based spacing (gaps, padding) resolves against the element's own font-size: set `fontSize`
524
+ on the container, or word gaps collapse.
525
+ - Masked text needs travel > 100% and container padding, or descenders stay visible.
526
+ - Load fonts through `@remotion/fonts` (brand your display typeface is local in `assets/fonts/`) so renders wait for them.
527
+
490
528
  ---
491
529
 
492
- ## Feedback format from the user
493
-
494
- Notes arrive as timecode + specific issue ("0:14 title lands 3 frames late, bounce too strong").
495
- Translate each note into token or timing changes; if a note conflicts with a law, say so and ask.
496
-
530
+ ## Feedback format from the user
531
+
532
+ Notes arrive as timecode + specific issue ("0:14 title lands 3 frames late, bounce too strong").
533
+ Translate each note into token or timing changes; if a note conflicts with a law, say so and ask.
534
+
497
535
  ---
498
536
 
499
537
  ## What is already installed
@@ -541,3 +579,21 @@ and add its name to `templates/BRIEF.md` so it can be asked for by name next tim
541
579
  The studio gets updates. When new components, scripts or rules ship, run `editing_os_update`
542
580
  and they are written into this project alongside your own work, which is never touched.
543
581
 
582
+ ## Updating the studio
583
+
584
+ New effects, fixes and rules ship as releases. The studio's release is in `.studio.json`. To update,
585
+ run this inside the studio folder — add `--dry-run` first to see what it would do:
586
+
587
+ ```bash
588
+ npx creator-editing-studio@latest update
589
+ ```
590
+
591
+ It never touches `src/design/tokens.ts`, your videos or your footage. Every other file is sorted:
592
+ files you never edited are replaced, files you edited are kept, and a file that both you and the
593
+ release changed is left exactly as it is, with the new version saved beside it in `.studio-update/`.
594
+ Merge those keeping your changes, delete `.studio-update/`, then `npm install` if `package.json`
595
+ changed, and `npm run check`.
596
+
597
+ Do not update by copying files across or comparing them by eye. The command knows every version of
598
+ every file ever shipped, which is the only way to tell your edit from an older release.
599
+