diff --git a/skills/meta/bespoke-composition.md b/skills/meta/bespoke-composition.md index 04b692b..c2cadf9 100644 --- a/skills/meta/bespoke-composition.md +++ b/skills/meta/bespoke-composition.md @@ -14,7 +14,7 @@ need so that what you build is correct, and distinct. ## When to use this skill (authoring mode is a proposal decision) -OpenMontage now separates three orthogonal axes, all locked at proposal: +OpenMontage separates three orthogonal axes, all locked at proposal: - `renderer_family` — creative grammar - `render_runtime` — technical engine (remotion / hyperframes / ffmpeg) @@ -26,8 +26,16 @@ output, localization variants, quick drafts, low-stakes internal clips — place sameness is fine and bespoke cost is unjustified. Present the choice to the user at proposal and log it in `decision_log` (`category: "composition_mode"`), the same way you present runtime. -If atelier is chosen, the stock scene-type catalog, the `hyperframes-registry` blocks, fixtures, -and any finished component are **off-limits** — they are frozen looks and reintroduce sameness. +### Atelier and the two runtimes + +The two runtimes treat "bespoke" differently — read this before assuming the doctrine maps the same way to both: + +- **Remotion** ships a `cut.type` registry of stock scenes (`text_card`, `stat_card`, `bar_chart`, …) dispatched by the `Explainer`/`CinematicRenderer` compositions. That's the templated default. **Atelier mode is the escape hatch** — `composition_mode: "atelier"` routes the render to `_render_via_atelier` and bypasses the registry entirely, so the agent hand-writes its own React composition under `projects//`. +- **HyperFrames is inherently atelier.** There is no cut-schema in HF — every composition is a hand-authored `index.html` with `data-*` timing attributes and a GSAP timeline you wrote. The registry (`hyperframes add`) is a *block* registry (grain overlays, transitions) — it's optional inputs to your composition, not a scene catalog dispatching the whole render. **When `render_runtime: "hyperframes"` is chosen, the piece is already atelier-style; `composition_mode: "atelier"` is implicit.** The principles in this skill (art direction, scene distinctness, no hero-component spine, distinctness review) apply equally. Render via `hyperframes_compose` (or `npx hyperframes render` for hand-authored compositions — see section 5). + +So: if `render_runtime == "remotion"` and the piece is hero work, log a `composition_mode: "atelier"` decision. If `render_runtime == "hyperframes"`, atelier is the default behavior and you don't need to argue for it; this skill still routes you through the same principles before you write the composition. + +If atelier is in play (either runtime), the stock Remotion `cut.type` catalog, `hyperframes-registry` finished blocks consumed as scenes (vs as raw inputs), fixtures, and any pre-baked creative component are **off-limits** — they are frozen looks and reintroduce sameness. ## The construction route @@ -82,19 +90,49 @@ Reach for **principle** skills, never finished animations: - The HyperFrames `references/motion-principles.md` — easing as emotion, timing as weight. ### 3. Reach for a richer vocabulary only when the concept demands it -Most scenes are Remotion primitives. Escalate when the *idea* needs it, not by default: -`gsap-*` (kinetic typography via SplitText, shape morph via MorphSVG, curved motion via -MotionPath, line-draw via DrawSVG, custom easing), `threejs-*` (3D), `d3-viz` (data-driven -custom charts — build the chart by hand; do **not** drop in the stock `bar_chart`/`line_chart`), -`manim-*` (math), `canvas-procedural-animation` (particles/weather). + +**On Remotion** — most scenes are Remotion primitives. Escalate when the *idea* needs it, not +by default: `gsap-*` (kinetic typography via SplitText, shape morph via MorphSVG, curved +motion via MotionPath, line-draw via DrawSVG, custom easing), `threejs-*` (3D), `d3-viz` +(data-driven custom charts — build the chart by hand; do **not** drop in the stock +`bar_chart`/`line_chart`), `manim-*` (math), `canvas-procedural-animation` (particles/weather). + +**On HyperFrames** — the vocabulary lives in `/hyperframes-animation`: 36+ atomic motion +**rules** (`kinetic-beat-slam`, `3d-text-depth-layers`, `motion-blur-streak`, +`physics-press-reaction`, `multi-phase-camera`, `depth-of-field-blur`, …), 15+ scene +**blueprints** (`kinetic-type-beats`, `comparison-split`, `dataviz-countup`, +`constellation-hub`, `ticker-takeover`, `device-surface-showcase`, …), 16 **transition** +families (`css-distortion`, `css-destruction`, `css-radial`, `css-light`, `css-mechanical`, +…), and **7 runtime adapters** under one composition: GSAP default, plus Lottie, Three.js, +Anime.js, CSS keyframes, WAAPI, and TypeGPU (GPU compute). The headline capability is +`adapters/html-in-canvas-patterns.md` — capture live HTML/CSS as a GPU texture and render +through WebGL/Three.js for cinematic bloom, shatter, liquid, portal effects. Use it for 1–3 +hero beats per video, not every beat. **Compose 2–4 distinct atomic rules per beat** and use +**at least 3 different easings across the piece** — that's the doctrine baked into the +animation skill, not optional polish. + +For HF creative direction (palette/type/narration/beat planning) read `/hyperframes-creative`. +For HF assets (TTS/BGM/SFX/transcription/background-removal) read `/hyperframes-media` or +`/media-use`. For HF CLI workflow (init/lint/validate/inspect/snapshot/beats/render) read +`/hyperframes-cli`. The `/hyperframes` router skill maps it all. ### 4. Get the engine mechanics right — the gotcha codex This is the only place you "reuse": the engine's solved problems. These are facts about how -Remotion works, not looks. Study `.agents/skills/remotion-best-practices` (19 rule files: -timing, transitions, text-animations, transparent video, fonts, audio, sequencing, measuring -text). You may also read the stock components in `remotion-composer/src/components/` **as a +the framework works, not looks. + +**For Remotion**, study `.agents/skills/remotion-best-practices` (19 rule files: timing, +transitions, text-animations, transparent video, fonts, audio, sequencing, measuring text). +You may also read the stock components in `remotion-composer/src/components/` **as a mechanics codex — to learn idioms, never to import or imitate a look.** +**For HyperFrames**, the composition contract is in `/hyperframes-core` (the `data-*` +timing attributes — `data-start`, `data-duration`, `data-track-index` — plus the +mandatory `class="clip"`, `data-composition-id`, `window.__timelines` registration, sub- +composition mounts). Run `npx hyperframes lint && npx hyperframes validate` after every +change — they catch missing root attrs, missing clip ids, GSAP-target unresolved, overlapping +tweens, and contrast failures before render. Use `npx hyperframes snapshot . --at ` +to spot-check beats visually before committing to a full render. + Recurring mechanics that bite if you don't know them: - **Determinism**: no `Math.random()` / `Date.now()` per frame — use Remotion `random(seed)` or a seeded helper, or particles/easing flicker across the render. @@ -105,7 +143,14 @@ Recurring mechanics that bite if you don't know them: public dir and reference via `staticFile`. Mirror the `resolveAsset` helper. - **GSAP-in-Remotion**: use a `paused` timeline and `.seek(frame/fps)` — never `requestAnimationFrame` — so frames render deterministically. -- **Fonts**: `loadFont()` from `@remotion/google-fonts/` at module scope, once. +- **Fonts (Remotion)**: `loadFont()` from `@remotion/google-fonts/` at module scope, once. +- **HF data-* contract**: every timed element needs `data-start`, `data-duration`, + `data-track-index`, AND `class="clip"` — without `clip` the framework won't manage + visibility and your element will be onscreen the whole timeline. Root `` needs + `data-composition-id`, `data-start="0"`, `data-duration`, `data-width`, `data-height`. + Every timeline must be `gsap.timeline({ paused: true })` and registered as + `window.__timelines[""]`. Use stable `id` attributes on clips and on + GSAP targets — `nth-of-type` selectors are flaky at validate time. - **Captions vs on-screen text — pick one role, never both for the same content.** Decide once per piece, before authoring: are captions adding meaning the spoken words can't carry (a number, a name, a translation, a quote attribution), OR are they accessibility subtitles @@ -114,26 +159,30 @@ Recurring mechanics that bite if you don't know them: amateurish even when the rest of the scene is beautiful. Empty `captions=[]` in props, or scope captions only to scenes where the on-screen text differs from what's being said. -### 5. Render through the atelier path (project-local, throwaway) -Bespoke scenes are **throwaway and project-local** — they never enter the shared `src/` registry. +### 5. Render through the runtime-appropriate bespoke path +Bespoke compositions are **throwaway and project-local** — they never enter any shared registry. -- Author under `remotion-composer/projects//` (gitignored). It needs its own Remotion entry - (`index.tsx` + a `Root` registering only this composition) so it reuses the composer's - `node_modules` and stays out of the global `Root.tsx`. The entry MUST live under - `remotion-composer/` for the bundler to resolve `remotion`. -- Keep media in a small per-project public dir and pass it as `public_dir` so renders don't copy - the bloated shared `public/`. -- Render via `video_compose` `operation="render"` with: +#### Remotion atelier path +- Author under `projects//` (gitignored). The render tool auto-stages your `.tsx`/`.ts` + source into `remotion-composer/projects//` via mtime-skip copy so webpack can resolve + `node_modules` — your source-of-truth stays under `projects/`. +- Scaffold with `python scripts/scaffold_atelier_project.py ` — emits engine + plumbing only (entry / Root / blank `Composition.tsx` / `art-direction.md` / props + template / README). Zero creative content; the placeholder is a deliberately ugly black + screen so the post-render review correctly refuses to ship the scaffold unauthored. +- Keep media in `projects//public/` and pass that as `bespoke.public_dir`. +- Render via `video_compose` `operation="render"`: ```json edit_decisions = { "render_runtime": "remotion", "composition_mode": "atelier", "bespoke": { - "entry": "remotion-composer/projects//index.tsx", + "entry": "projects//index.tsx", "composition_id": "", - "props_path": "", - "public_dir": "", + "props_path": "", + "public_dir": "/public/>", + "art_direction": "", "scale": 0.5, // 0.5 for a fast draft; drop for the 1080p final "crf": 18, // crisp final "concurrency": 8 @@ -142,6 +191,26 @@ edit_decisions = { ``` No `asset_manifest` or `cuts` are required in atelier mode — the composition owns its own assets. +The tool's `_run_atelier_checks` fails the render if any source file imports from the stock +registry (`src/components`, `src/Explainer`, etc.), and warns if `art_direction` is missing. + +#### HyperFrames path +- Scaffold with `npx hyperframes init ` (run from `projects/`). HF init generates + `index.html`, `meta.json`, `package.json`, and a per-project `CLAUDE.md` that auto-routes + the agent into `/hyperframes` — so the next session knows exactly which sub-skills to load. +- Author `index.html` by hand. Every clip needs `class="clip"` + the three `data-*` timing + attrs + a stable `id`. Mount sub-compositions via `data-composition-src` once the timeline + on one file grows past 4 clips (the lint catches density). +- For music-driven pieces, run `npx hyperframes beats .` after dropping the track in + `assets/` — it emits `beats/