diff --git a/README.md b/README.md
index c2c1879d5..0ca718a23 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ npx skills add heygen-com/hyperframes
This teaches your agent (Claude Code, Cursor, Gemini CLI, Codex) how to write correct compositions and GSAP animations. In Claude Code, the skills register as slash commands — invoke `/hyperframes` to author compositions, `/hyperframes-cli` for CLI commands, and `/gsap` for animation help.
-For Claude Design, use the GitHub-hosted entry point at [`skills/claude-design-hyperframes/SKILL.md`](https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md) and let Claude fetch the repo's `skills/` tree from there. See the [Claude Design guide](https://hyperframes.heygen.com/guides/claude-design).
+For Claude Design, download [`skills/claude-design-hyperframes/SKILL.md`](https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md) and attach it to your chat. Claude Design produces a valid first draft; refine it in any AI coding agent. See the [Claude Design guide](https://hyperframes.heygen.com/guides/claude-design).
For Codex specifically, the same skills are also exposed as an [OpenAI Codex plugin](./.codex-plugin/plugin.json) — sparse-install just the plugin surface:
@@ -184,14 +184,14 @@ HyperFrames ships [skills](https://github.com/vercel-labs/skills) that teach AI
npx skills add heygen-com/hyperframes
```
-| Skill | What it teaches |
-| --------------------------- | ------------------------------------------------------------------------------------------------------- |
-| `claude-design-hyperframes` | Claude Design entry point that pulls the upstream `skills/` tree and standardizes player-based previews |
-| `hyperframes` | HTML composition authoring, captions, TTS, audio-reactive animation, transitions |
-| `hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts, doctor |
-| `hyperframes-registry` | Block and component installation via `hyperframes add` |
-| `website-to-hyperframes` | Capture a URL and turn it into a video — full website-to-video pipeline |
-| `gsap` | GSAP animation API, timelines, easing, ScrollTrigger, plugins, React/Vue/Svelte, performance |
+| Skill | What it teaches |
+| --------------------------- | ------------------------------------------------------------------------------------------------------------------ |
+| `claude-design-hyperframes` | Template-first Claude Design skill — pre-valid skeletons, produces video drafts for refinement in any coding agent |
+| `hyperframes` | HTML composition authoring, captions, TTS, audio-reactive animation, transitions |
+| `hyperframes-cli` | CLI commands: init, lint, preview, render, transcribe, tts, doctor |
+| `hyperframes-registry` | Block and component installation via `hyperframes add` |
+| `website-to-hyperframes` | Capture a URL and turn it into a video — full website-to-video pipeline |
+| `gsap` | GSAP animation API, timelines, easing, ScrollTrigger, plugins, React/Vue/Svelte, performance |
## Contributing
diff --git a/docs/guides/claude-design.mdx b/docs/guides/claude-design.mdx
index 069652222..91ccee584 100644
--- a/docs/guides/claude-design.mdx
+++ b/docs/guides/claude-design.mdx
@@ -1,31 +1,37 @@
---
title: Claude Design
-description: "Use HyperFrames from Claude Design with a GitHub-hosted skill entry point that pulls the repo's existing skills and preview patterns."
+description: "Create HyperFrames video drafts in Claude Design using a template-first skill, then refine in any AI coding agent."
---
-Claude Design needs a different setup than Claude Code, Cursor, or Codex. The local `npx skills add heygen-com/hyperframes` flow is the right path for coding agents. For Claude Design, attach the HyperFrames skill file to your chat — empirically that produces sharper, more rule-compliant output than pasting the URL.
-
-## Get the skill
-
-
-
- Save the file, then drag it into a Claude Design chat as an attachment.
-
-
- Read the skill, copy sections, or link to it in a prompt.
-
-
+Claude Design produces a **valid first draft** of a HyperFrames video — brand identity, scene content, layout, animations, and transitions. You then download the ZIP and refine in any AI coding agent (Claude Code, Cursor, Codex, Windsurf, etc.) with linting and live preview.
+
+## Get started
+
+
+
+ Save [`SKILL.md`](https://raw.githubusercontent.com/heygen-com/hyperframes/main/skills/claude-design-hyperframes/SKILL.md) to your computer.
+
+
+ Start a new chat at [claude.ai](https://claude.ai) with Claude Design enabled.
+
+
+ Drag the SKILL.md file into the chat. Describe what you want — include screenshots, brand assets, or a palette if you have them.
+
+
+ Claude Design produces `index.html`, `preview.html`, `README.md`, and `DESIGN.md`. Download the ZIP.
+
+
+ Open the project in Claude Code, Cursor, Codex, or any agent with terminal access for animation polish, timing, and production QA.
+ ```bash
+ npx skills add heygen-com/hyperframes # install skills (one-time)
+ npx hyperframes lint # should pass with zero errors
+ npx hyperframes preview # open the studio
+ ```
+
+
- **Prefer attaching the file over pasting the URL.** Claude Design reads file attachments natively with detail preserved. URL-driven runs produce usable output but consistently miss more rules than attachment-driven runs.
+ **Attach the file, don't paste the URL.** Claude Design reads file attachments natively with detail preserved. URL-driven runs produce usable output but consistently miss more rules.
## Which setup to use
@@ -36,127 +42,99 @@ Claude Design needs a different setup than Claude Code, Cursor, or Codex. The lo
| Claude Code | `npx skills add heygen-com/hyperframes`, then use `/hyperframes` |
| Cursor / Codex / Gemini CLI | `npx skills add heygen-com/hyperframes` |
-## Prompt shape for Claude Design
-
-Claude Design does not use slash commands. Lead with the skill file (attached or URL), describe the video, and ideally give Claude Design something to synthesize from — screenshots, a brand PDF, a reference video, a pasted palette, or at minimum a vibe in words.
+## How the skill works
-The skill reads inputs in this order of reliability: **attachments → pasted content → web research → URLs**. A modern SPA homepage returns almost nothing via `web_fetch` because JavaScript isn't executed, so brand-accurate output on brief like "make a video for linear.app" depends on attaching screenshots or letting Claude Design search for the brand's blog, press, or Wikipedia.
+The skill gives Claude Design **pre-valid HTML skeletons** — the structural rules (data attributes, timeline registration, scene visibility, preview token forwarding) are already embedded. Claude Design fills in the creative work:
-Strong Claude Design prompts usually include:
+1. **Palette + typography** — CSS custom properties on `:root`
+2. **Scene content** — text, images, layout inside `.scene-content` wrappers
+3. **Animations** — GSAP entrance tweens and mid-scene activity
+4. **Transitions** — hard cuts for most scenes, shader transitions at 2-3 key moments
-- the attached skill file (or its URL)
-- the source material: screenshots, a brand PDF, a reference video, pasted copy, or a URL
-- duration and aspect ratio
-- tone or visual direction (if you have one — otherwise let Claude Design ask)
-- explicit deliverables: `index.html`, `preview.html`, `README.md`
+This template-first approach means the output passes `npx hyperframes lint` with zero errors on first download — Claude Code can start refining immediately without structural fixes.
-## Copy-paste prompts
+## Example prompts
-
+
```text
- Use the attached skill. Make a 30-second 16:9 product walkthrough for my app,
- matching the design in these screenshots. 5 scenes: hero, three features,
- closing CTA. Shader transitions between scenes.
+ Use the attached skill. I just shipped dark mode for my app. Make me a
+ 15-second Instagram reel announcing it.
+
+ - App name: Taskflow
+ - Primary color: #6C5CE7
+ - The vibe is clean, minimal, dark
+ - Key stat: "47% of users requested this"
```
-
+
```text
- Use the attached skill. 30s hero reel with this copy for each scene:
+ Use the attached skill. 25-second LinkedIn video for my startup.
- 1. "The fast web broke us."
- 2. "Every app optimized for attention. None for thought."
- 3. "Something is changing. Blogs are back."
- 4. "Welcome to the slow web."
+ Problem: Sales teams waste 3 hours/day on manual CRM updates.
+ Solution: AutoCRM — AI that logs every call, email, and meeting.
+ Traction: 200+ teams, $1.2M ARR, 18% MoM growth.
+ CTA: autocrmhq.com
- Dark theme, editorial, serif typography (not Playfair).
+ Professional but not corporate. Think Linear or Vercel energy.
```
-
+
```text
- Use the attached skill. Make a 30-second launch video for Orbit.
- ```
+ Use the attached skill. 10-second reel. Just one big number:
- The skill will ask ONE short question offering five input channels
- (screenshot, PDF, reference video, vibe word, must-have element) plus
- a "just build" escape hatch before generating.
+ "$4.2 billion processed in Q1 2026"
+
+ Dark background, the number should animate up from zero. Subtle,
+ confident. End with logo placeholder and "stripe.com"
+ ```
-
+
```text
- Use the HyperFrames Claude Design skill at
- https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md
- and turn https://www.anthropic.com/news/claude-design-anthropic-labs into a
- 45-second editorial explainer. Keep copy close to the article's real headlines.
+ Use the attached skill. Make a 30-second launch video for Orbit.
```
+
+ The skill asks ONE short clarifying question before generating.
-## Preview with `@hyperframes/player`
-
-When Claude Design generates a `preview.html`, it embeds the composition with `` and forwards the Claude Design sandbox's preview token into the iframe src. Without the token forward, the in-pane preview renders black (the sandbox serves a `"preview token required"` placeholder to the iframe).
-
-Copy this template verbatim:
-
-```html
-
-
-
-
- HyperFrames Preview
-
-
-
-
-
-
-
-
-```
+## What to include in your prompt
-When `location.search` is empty (opened locally, outside Claude Design's sandbox), the token-forward line is a no-op and the player loads `./index.html` as expected.
+Claude Design reads inputs in this order of reliability: **attachments > pasted content > web research > URLs**.
-The composition (`index.html`) must also pre-load the HyperFrames runtime right after GSAP so the player can drive playback inside Claude Design's sandbox:
+| Input type | What it gives Claude Design |
+| --- | --- |
+| Screenshots / PDFs / brand guides | Palette, typography, UI patterns, tone — strongest source |
+| Pasted hex codes, typefaces, copy | Authoritative for what it covers |
+| Brand name (well-known) | Claude Design can research blogs, press, Wikipedia |
+| SPA URL (React/Vue homepage) | Returns near-empty shell — pivot to blog/press instead |
-```html
-
-
-```
+The more specific your prompt, the better the output. Include palette, fonts, duration, and scene ideas when you have them.
-If a classic script tag is needed instead of ESM, use the global player build with the same token-forwarding script:
-
-```html
-
-
-
-```
+## Known limitations
+
+- **In-pane preview** — scrubbing is unreliable in Claude Design's iframe sandbox. Download and use `npx hyperframes preview` locally for reliable playback.
+- **No linting** — Claude Design can't run `npx hyperframes lint`. The template-first skeletons handle structural validity, but the self-review checklist is the only QA before download.
+- **No shaders on vertical** — HyperShader's WebGL canvas is hardcoded to 1920x1080. Vertical (1080x1920) compositions use hard cuts only.
+- **3 fetch limit** — Claude Design limits web fetches per turn. All critical rules are inlined in the skill; external references are for edge cases only.
+- **Seeking backwards** — scrubbing backwards in the in-pane preview can show blank frames (async capture race condition). Forward seeking usually works.
-See [`@hyperframes/player`](/packages/player) for the full API and framework examples.
+## The handoff to your coding agent
-## What the skill teaches Claude Design
+Claude Design's output is a valid first draft. Open it in Claude Code, Cursor, Codex, or any AI coding agent with terminal access:
+
+```bash
+npx skills add heygen-com/hyperframes # one-time setup
+npx hyperframes lint # verify structure
+npx hyperframes preview # open the studio
+```
-The skill is self-contained — it includes every HyperFrames-specific contract and every known Claude Design sandbox workaround, so Claude Design rarely needs to fetch additional references. Highlights:
+Then iterate:
-- An explicit opening redirect: Claude Design is told NOT to reach for its default video artifacts (`copy_starter_component` with `kind: "animations.jsx"`, the built-in "Animated video" skill, React + Babel JSX, hand-rolled scale-to-fit stage wrappers). This is the single change that most reliably keeps Claude Design on the HyperFrames path
-- Correct `data-*` composition structure, the clip contract on scenes, and paused GSAP timelines registered on `window.__timelines`
-- Shader-transition timing rules: transitions must span the scene boundary (not start at it), animated content must be wrapped in `
` so its pre-animation state doesn't leak into the WebGL texture, and the `scenes.length === transitions.length + 1` invariant (with `flash-through-white` as the invisible-bridge escape hatch)
-- Sandbox-compatible preview: token-forwarding `preview.html` template, runtime pre-load in `index.html`, `data-composition-id` ↔ `__timelines` key match (and a convention that the root element's DOM `id` matches too)
-- Attachment-first input model: read screenshots / PDFs / reference videos when provided, otherwise ask one short clarifying question before generating
-- A banned-font list (including Fraunces, Inter Tight, and common AI defaults) plus a banned-pairings line to break the training-data monoculture
-- Deterministic render-safe animation choices (no `Date.now()`, no unseeded `Math.random()`, no `repeat: -1`, no `stagger: { from: "random" }`)
-- Four worked-example anti-patterns with WRONG/RIGHT code pairs — exit tweens before shader transitions, non-deterministic `stagger` origins, absolute-positioned content containers, and SVG filter data URLs as `background-image` (the last one causes `SecurityError` on Safari + cross-origin iframe environments)
-- A `README.md` template for the end user with `npx hyperframes doctor` / `preview` / `render` commands and FFmpeg install instructions
+- "Make scene 3's entrance snappier"
+- "Add a counter animation to the stat in scene 5"
+- "Tighten the pacing — scenes 4 and 6 feel too long"
+- "Change the shader on transition 2 to glitch"
## Next steps
diff --git a/docs/guides/prompting.mdx b/docs/guides/prompting.mdx
index 69a3cc765..47e94d479 100644
--- a/docs/guides/prompting.mdx
+++ b/docs/guides/prompting.mdx
@@ -29,13 +29,18 @@ In Claude Code, restart the session after installing. Skills register as **slash
## Claude Design
-Claude Design uses a different setup. Instead of local slash commands, point it at the GitHub-hosted Claude Design entry point for HyperFrames:
+Claude Design uses a different setup. Download [`SKILL.md`](https://raw.githubusercontent.com/heygen-com/hyperframes/main/skills/claude-design-hyperframes/SKILL.md) and **attach it to your chat** (don't paste the URL — file attachments produce better output):
```text
-Use https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md and make a 30-second product video about [topic]. Deliver index.html, preview.html, and README.md.
+Use the attached skill. 25-second LinkedIn video for my startup.
+
+Problem: Sales teams waste 3 hours/day on manual CRM updates.
+Solution: AutoCRM — AI that logs every call, email, and meeting.
+Traction: 200+ teams, $1.2M ARR, 18% MoM growth.
+CTA: autocrmhq.com
```
-That entry point tells Claude Design to fetch the broader [`skills/`](https://github.com/heygen-com/hyperframes/tree/main/skills) tree, apply the same HyperFrames rules, and use `@hyperframes/player` for preview pages. See the [Claude Design guide](/guides/claude-design) for the recommended prompt shape.
+Claude Design produces a valid first draft (brand identity, scene content, animations, transitions). Download the ZIP and refine in any AI coding agent with `npx hyperframes preview` running. See the [Claude Design guide](/guides/claude-design) for the full workflow.
## The two prompt shapes
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
index 86e815a48..fb40364a7 100644
--- a/docs/quickstart.mdx
+++ b/docs/quickstart.mdx
@@ -16,7 +16,7 @@ npx skills add heygen-com/hyperframes
This teaches your agent (Claude Code, Cursor, Gemini CLI, Codex) how to write correct compositions and GSAP animations. In Claude Code the skills register as slash commands — `/hyperframes` for composition authoring, `/hyperframes-cli` for CLI commands, and `/gsap` for animation help. Invoking the slash command loads the skill context explicitly, which produces correct output the first time.
- Claude Design uses a different entry path. Point Claude Design at [`skills/claude-design-hyperframes/SKILL.md`](https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md) so it can fetch the repo's `skills/` tree and generate a HyperFrames handoff. See the [Claude Design guide](/guides/claude-design).
+ Claude Design uses a different entry path. Download [`skills/claude-design-hyperframes/SKILL.md`](https://github.com/heygen-com/hyperframes/blob/main/skills/claude-design-hyperframes/SKILL.md) and attach it to your Claude Design chat. It produces a valid first draft you can refine in any AI coding agent. See the [Claude Design guide](/guides/claude-design).
### Try it: example prompts
diff --git a/skills/claude-design-hyperframes/SKILL.md b/skills/claude-design-hyperframes/SKILL.md
index 3ec47654c..35e45f1bf 100644
--- a/skills/claude-design-hyperframes/SKILL.md
+++ b/skills/claude-design-hyperframes/SKILL.md
@@ -1,677 +1,445 @@
---
name: claude-design-hyperframes
-description: Claude Design entry point for HyperFrames. Produce renderable HyperFrames videos in Claude Design with a working in-pane preview. Use for any request to create a video, animation, launch teaser, editorial explainer, product tour, social reel, or motion deliverable.
+description: Use when running inside Claude Design specifically. Produces a valid, brand-accurate HyperFrames video draft using pre-valid skeletons. For Claude Code / Cursor / Codex, use the `hyperframes` skill instead.
---
-# Claude Design + HyperFrames
+# Claude Design + HyperFrames (Template-First)
-For this project, your medium is **HyperFrames compositions**: plain HTML + CSS + a paused GSAP timeline. A separate CLI (`npx hyperframes render index.html`) turns the HTML into an MP4. You are authoring the HTML files — the user runs the CLI locally. You do NOT need a CLI environment to produce these files.
+Your medium is **HyperFrames compositions**: plain HTML + CSS + a paused GSAP timeline. The CLI (`npx hyperframes render index.html`) turns the HTML into an MP4. You author the HTML -- the user renders locally.
-**HyperFrames replaces your default video-artifact workflow for this project.** When the user asks for a video, animation, launch teaser, editorial explainer, product tour, social reel, or any motion deliverable:
-
-- Do **NOT** call `copy_starter_component` with `kind: "animations.jsx"`. The animations.jsx starter is the wrong format here — HyperFrames uses plain HTML + GSAP, not React Sprites.
-- Do **NOT** invoke the built-in "Animated video" skill. HyperFrames replaces it for this project.
-- Do **NOT** use React, Babel, or `
-
-```
+Use at least 3 different eases per scene. Don't default to `power2.out` on everything.
-**Scene-count invariant — `scenes.length === transitions.length + 1`:** HyperShader enforces this at init. Pick one anchor scene BEFORE the first transition, and one anchor AFTER each transition. A video with three act-boundary transitions needs exactly four anchor scenes. Scenes between anchors (non-bracketing, runtime-managed) carry `style="visibility:hidden;"` instead of `style="opacity:0;"` — they're not HyperShader-managed so nothing animates their opacity back to 1.
+| Feeling | Ease | Duration |
+| ---------- | --------------- | -------- |
+| Smooth | `power2.out` | 0.4-0.6s |
+| Snappy | `power4.out` | 0.2-0.3s |
+| Bouncy | `back.out(1.6)` | 0.3-0.5s |
+| Dramatic | `expo.out` | 0.3-0.5s |
+| Dreamy | `sine.inOut` | 0.5-0.8s |
+| Mechanical | `steps(5)` | 0.3-0.5s |
-The simplest working pattern: list only the scene just before AND just after each shader cut. Do NOT list every scene in Act II just because they "span" a transition — that violates the invariant. If you genuinely need MORE listed anchors than real shader transitions (rare — e.g., tracking an additional fade beat that's not a visible shader bridge), insert `{ shader: "flash-through-white", duration: 0.01 }` as an invisible no-op bridge to satisfy the invariant. This is a workaround; the cleaner fix is almost always to drop the extra anchor.
+---
-**Transition timing (critical — the scene boundary must fall INSIDE the transition window):**
+## Step 4: Transitions
-Scene windows are half-open (`[start, start+duration)`). At time `B` (the boundary), the runtime has already flipped the outgoing scene to `visibility:hidden`. If `transition.time === B`, `html2canvas` captures a blank outgoing texture → shader transitions from blank → incoming → visible blink.
+### The professional rule: most cuts are hard cuts
-Rule: `transition.time < B` AND `transition.time + duration > B`. Simplest — center it: `transition.time = B - duration/2`. Example: scene-1 ends at 6, duration 0.5 → `time: 5.75`.
+In professional video, ~95% of scene changes are hard cuts. Effect transitions (shaders, dissolves) are reserved for 2-3 key moments — a hero reveal, an energy shift, the CTA landing. Using a shader on every cut is the video equivalent of bolding every word in a paragraph.
-**Scene visibility: HANDS OFF.** HyperShader owns scene `opacity` end-to-end. Do NOT add `tl.set(#scene-N, {autoAlpha: …}, …)` on scene containers. If you do, you create the same visibility race that produces the blink.
+The skeleton pre-wires **2 shader transitions at key moments** and **hard cuts everywhere else**. This gives you varied rhythm: cut-cut-SHADER-cut-cut-SHADER-cut.
-### Sub-compositions — default NO for videos ≤ 3 minutes
+### Three transition types
-Default to a single `index.html` with scenes tiled inline. 30-second to 2-minute compositions fit cleanly in one file (~1500–2000 lines). Single file = single HyperShader instance = no canvas conflicts = everything works.
+**Hard cut (default -- most scenes use this):**
+No transition code needed. Scene N disappears, scene N+1 appears. The entrance animations on the new scene do all the visual work. This is the professional default.
-Split into sub-compositions ONLY when one of these is true:
+**Shader transition (2-3 per video -- hero/climax/CTA moments):**
+Pre-wired in the skeleton at key positions. HyperShader captures both scenes as textures and composites them pixel-by-pixel via WebGL.
-- Video length > 3 minutes AND you need organizational structure.
-- You're extracting a REUSABLE sub-comp that appears in multiple places (chart block, logo outro).
-- A single scene is so complex it deserves its own file (full UI recreation, heavy data-vis).
+**When to use shaders vs hard cuts:**
-If you do split, **HyperShader lives at the ROOT `index.html` ONLY** — never inside a sub-composition. HyperShader hardcodes `#gl-canvas` as its canvas ID (see the canvas creation path in `packages/shader-transitions/src/hyper-shader.ts`); multiple HyperShader instances can't share one canvas. When a sub-comp's HyperShader fails silently on canvas conflict, its fallback code calls `document.querySelectorAll(".scene")` document-wide and sets every scene's opacity to 0 — corrupting visibility across the whole document. Symptom: only scene-1 of each act shows, scenes 2+ never appear.
+| Use shader for | Use hard cut for |
+| ------------------------------- | ------------------------------------ |
+| Hero reveal / product unveil | Connective scenes between features |
+| Major energy shift or act break | Rapid-fire lists or stats |
+| CTA / final brand moment | 3+ consecutive quick scene changes |
+| Any moment the music punctuates | Scenes where pacing should feel fast |
-#### Sub-composition file shape
+Rule of thumb: a 6-8 scene video wants **2 shader transitions** and the rest hard cuts.
-Every sub-comp file in `compositions/` is wrapped in a ``. The template's contents are INERT in the browser by spec — the runtime extracts and nests them into the parent at render time. A standalone `index.html` (the main composition) does NOT use ``; the data-composition-id div goes directly in ``.
+### Adjusting shader transitions
-```html
-
-
-
-
+**Change shader names** -- pick from these 14:
-
-
-```
+**Match shaders to energy:**
-#### Parent `index.html` wiring
+| Energy | Shaders |
+| -------------------- | ---------------------------------------------------- |
+| Calm, editorial | `cross-warp-morph`, `light-leak`, `domain-warp` |
+| Medium, professional | `cinematic-zoom`, `whip-pan`, `sdf-iris` |
+| High, aggressive | `glitch`, `chromatic-split`, `ridged-burn` |
+| Ethereal, mysterious | `gravitational-lens`, `ripple-waves`, `swirl-vortex` |
-The parent mounts each sub-comp via `data-composition-src` on an empty div that carries the clip contract:
+**Adjust transition timing** -- when you change scene durations, recalculate each transition's `time`:
-```html
-
+```
+transition.time = scene_boundary - (transition.duration / 2)
```
-Three rules when splitting:
-
-1. `` wrapper required on every sub-comp. Contents are inert; the runtime extracts them.
-2. The `data-composition-id` on the sub-comp's inner root div MUST match BOTH (a) the parent container's `data-composition-id` AND (b) the key in `window.__timelines[...]` inside the sub-comp's script.
-3. Tween positions in a sub-comp are LOCAL to that sub-comp (0 = its start). The parent auto-offsets by the container's `data-start`. Never manually add sub-timelines to the root timeline.
+Example: scene-3 ends at 8s, transition duration 0.5s -> `time: 7.75`.
-Since the sub-comps in this pattern don't use HyperShader (by the rule above), their non-first scenes carry `style="visibility:hidden;"` only — see "Scene initial visibility" above for why.
+**Minimum transition duration: 0.3s.** Sweet spot is 0.5s.
-### Determinism ❌ / ✅
+### How the skeleton handles this
-The render engine seeks to exact frames and expects pixel-identical output on every repeat render. Violations produce broken output.
+The skeleton only lists **anchor scenes** (the ones bracketing shader transitions) in `HyperShader.init()`. Anchor scenes use `style="opacity:0;"` because HyperShader manages their opacity. Non-anchor scenes use `style="visibility:hidden;"`.
-| ❌ Never | ✅ Use instead |
-| ------------------------------------------ | ----------------------------------------------------- |
-| `Date.now()`, `performance.now()` | `tl.time()` inside `onUpdate`, or hard-coded timing |
-| `Math.random()` unseeded | seeded PRNG (e.g. mulberry32) with a known seed |
-| `setInterval`, `setTimeout` in timeline | timeline tweens + `onUpdate` callbacks |
-| `repeat: -1` on any tween or timeline | `repeat: Math.ceil(duration / cycleDuration) - 1` |
-| Timelines built in `async`/`await` wrapper | Construct synchronously at page load |
-| `video.play()`, `audio.play()` in code | Framework owns media playback |
-| Animating `visibility` or `display` | `autoAlpha` (animates opacity AND toggles visibility) |
+**CRITICAL — two bugs cause "invisible middle scenes" if you don't handle them:**
-### Motion rules (HyperFrames-native, non-negotiable)
+1. **Non-anchor scenes need explicit `tl.set` visibility toggles.** Without them, the scene container stays at `visibility:hidden` and child animations play inside an invisible parent.
-Inherited from `skills/hyperframes/SKILL.md#Rules-Non-Negotiable`:
+2. **The first anchor scene in each shader group needs `tl.set("#sN", { opacity: 1 }, )`.** HyperShader browser mode does NOT auto-show the first anchor. It stays at `opacity:0` for its entire window. Every demov4 composition has this bug.
-- **GSAP visual properties only.** Animate `opacity`, `x`, `y`, `scale`, `rotation`, `color`, `transforms`. Do NOT animate `visibility` or `display` directly (use `autoAlpha`).
-- **One paused timeline per composition.** `{ paused: true }`. Register on `window.__timelines[""]`. Never call `.play()`.
-- **Vary eases** — at least 3 different eases per scene. Don't default to `power2.out` on everything.
-- **Offset first tween 0.1–0.3s.** Zero-delay entrances feel like jump cuts.
-- **Exit animations BANNED** except on the final scene. The transition IS the exit. See the code examples below — this is the single most frequently-violated rule in generated output.
+The skeleton pre-wires these toggles for every non-anchor scene using **`autoAlpha`** (not `visibility`):
-### Motion anti-patterns (observed in generated output, with fixes)
+```js
+// --- Non-anchor scene toggles (REQUIRED — must use autoAlpha, not visibility) ---
+tl.set("#s1", { autoAlpha: 0 }, 2.5); // hide s1 at its end time
+tl.set("#s2", { autoAlpha: 1 }, 2.5); // show s2 at its start
+tl.set("#s2", { autoAlpha: 0 }, 5.0); // hide s2 at its end
+tl.set("#s3", { autoAlpha: 1 }, 5.0); // show s3 at its start
+tl.set("#s3", { autoAlpha: 0 }, 7.5); // hide s3 at its end
+```
-These four patterns keep appearing in generated compositions despite the rules above. Each one is observed in real outputs; each has a known-clean replacement. Pattern-match these, not just the prose rules.
+**Why `autoAlpha` and NOT `visibility`:** When any shader transition fires, HyperShader blanks ALL `.scene` elements to `opacity:0`. If a non-anchor scene only toggles `visibility`, the blanket reset poisons its `opacity` — the scene becomes `visibility:visible` but `opacity:0` (invisible). `autoAlpha` sets BOTH `opacity` AND `visibility` in one call, overriding the blanket reset.
-#### Anti-pattern 1: Exit tween before a shader transition
+**Rules:**
-The shader's `captureScene(fromScene)` runs `html2canvas` on the outgoing scene at transition time. If you've animated content to `opacity: 0` (or `autoAlpha: 0`, or off-screen) before the transition fires, `html2canvas` captures an empty scene. The shader morphs from an empty outgoing texture → the incoming scene, which looks like "the content vanished, then the transition happened." This is independent of whether the shader itself works — it's a composition-level bug.
+- Every non-anchor scene gets `tl.set("#sN", { autoAlpha: 1 }, )` AND `tl.set("#sN", { autoAlpha: 0 }, )`
+- Scene 1 gets only a hide at its end time (it starts visible)
+- Anchor scenes do NOT get autoAlpha toggles — HyperShader owns their opacity
+- When you add or remove scenes, update these toggles to match
-This matches industry practice: in Remotion's ``, in the GSAP community's own guidance, and in HyperFrames' core `references/transitions.md` — the transition component owns the visual handoff. The scene's content does not animate its own exit.
+Example for an 8-scene video with shaders at s4→s5 and s7→s8:
-```js
-// ✖ WRONG — card fades to 0 before transition at t=17.80 fires.
-// Shader captures an empty phone. User sees the card disappear
-// 0.85s before the transition, then an empty-phone-to-next-scene morph.
-tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 16.5);
-tl.to("#s6-card", { autoAlpha: 0, duration: 0.25 }, 16.95); // BANNED
+- Anchor scenes: s4, s5, s7, s8 (listed in HyperShader `scenes` array, use `opacity:0`)
+- Non-anchor scenes: s1, s2, s3, s6 (NOT in HyperShader, use `visibility:hidden`, with explicit `tl.set` toggles)
+- Scene 1 has no inline style (visible from t=0)
-// HyperShader transition at 17.80 captures #s6 with card invisible
-```
+### Adding or removing shader transitions
-```js
-// ✓ RIGHT — mid-scene swipe gesture, then a different beat holds the final
-// frame. Card moves but stays visible. Transition handles the actual exit.
-tl.to("#s6-card", { x: 180, rotation: 14, duration: 0.55, ease: "power3.in" }, 15.3);
-tl.from("#s6-check", { scale: 0, duration: 0.3, ease: "back.out(2)" }, 15.6);
-tl.from("#s6-match-stamp", { scale: 1.5, autoAlpha: 0, duration: 0.4 }, 16.1);
-// scene 6 ends at 18.0 with the matched-stamp + pulsing check button visible.
-// HyperShader transition at 17.80 captures a FULL scene → clean morph.
-```
+To add a shader transition between two scenes:
-Common trap: "I want to show a swipe gesture, so the card has to exit." No — the swipe gesture happens mid-scene, at 60–70% of scene duration. The last 30% of the scene shows the RESULT of the swipe (a match stamp, a confirmation, a badge). Keep something visible at transition time. If there's nothing logically left to show, the scene is too long — shorten it.
+1. Add both scene IDs to the `scenes` array in `HyperShader.init()`
+2. Add a transition object to the `transitions` array
+3. Change both scenes from `visibility:hidden` to `opacity:0`
+4. Invariant: `scenes.length === transitions.length + 1`
-#### Anti-pattern 2: Non-deterministic `stagger` origin
+To remove a shader transition (make it a hard cut instead):
-```js
-// ✖ WRONG — `from: "random"` picks a random origin at timeline-construction
-// time using GSAP's internal unseeded random. Two renders of the same
-// composition produce different stagger orderings. Fails PSNR regression
-// tests and violates the deterministic-render rule.
-tl.from(
- "#s12 .card",
- {
- scale: 0.7,
- autoAlpha: 0,
- y: 40,
- duration: 0.45,
- stagger: { each: 0.04, from: "random" }, // BANNED
- },
- 34.55,
-);
-```
+1. Remove the scene IDs from `scenes` (unless they're also anchors for another transition)
+2. Remove the transition from `transitions`
+3. Change affected scenes from `opacity:0` to `visibility:hidden`
-```js
-// ✓ RIGHT — deterministic stagger origins. All of these are safe.
-tl.from(
- "#s12 .card",
- { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "start" } },
- 34.55,
-); // natural order
+**BANNED: invisible bridge transitions.** Never pad with `flash-through-white` at 0.01s.
-tl.from(
- "#s12 .card",
- { scale: 0.7, autoAlpha: 0, y: 40, duration: 0.45, stagger: { each: 0.04, from: "center" } },
- 34.55,
-); // ripple outward
+---
-tl.from(
- "#s12 .card",
- {
- scale: 0.7,
- autoAlpha: 0,
- y: 40,
- duration: 0.45,
- stagger: { each: 0.04, grid: [3, 5], from: [0, 0] },
- },
- 34.55,
-); // grid-aware
-```
+## Step 5: Verify the preview + deliver
-If you truly need pseudo-random ordering (rare), pre-shuffle the cards in the markup using a seeded PRNG like mulberry32 — the ordering is then committed to the DOM and deterministic forever.
+**Gate:** Preview plays start to finish. All scenes visible. No blinking. Text readable.
-#### Anti-pattern 3: Centering content with `position: absolute; top; left` on `.scene-content`
+### Verify in the preview pane
-```css
-/* ✖ WRONG — absolute-positioned content container with hardcoded pixels.
- Renders at 1920×1080 but overflows at any other aspect ratio. Also
- pushes you toward absolute-positioning every child, which is fragile. */
-.scene-content {
- position: absolute;
- top: 200px;
- left: 160px;
- width: 1920px;
- height: 1080px;
-}
-```
+Scrub through every scene and check:
-```css
-/* ✓ RIGHT — flex-filled container with padding for the positioning.
- Works at any aspect ratio. Children flow naturally. */
-.scene-content {
- width: 100%;
- height: 100%;
- padding: 120px 160px;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 24px;
- box-sizing: border-box;
-}
-```
+1. Does scene 1 appear immediately? (If black: runtime not loaded, or `__timelines` key mismatch)
+2. Do shader transitions fire cleanly? (If blinking: transition too short, or exit animation before transition)
+3. Is all text readable against its background?
+4. Does every scene have motion during its hold? (If static: missing mid-scene activity)
+5. Do animations play in the correct order?
-See `skills/hyperframes/SKILL.md#Layout-Before-Animation` for the full rationale — in short: position every element at its final landing state first, then `gsap.from()` the entrance animating TO that position.
+### Troubleshooting: preview is black
-#### Anti-pattern 4: SVG filter data URLs used as `background-image` (grain, noise, turbulence)
+| Symptom | Cause | Fix |
+| ----------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| All black | Runtime script missing or wrong order | Check GSAP loads before runtime in `` |
+| All black | `__timelines` key doesn't match `data-composition-id` | Both must be `"main"` |
+| All black | Token not forwarded in preview.html | Check `location.search` is appended to src |
+| Scene doesn't appear | Wrong `data-start` / `data-duration` | Check scene windows tile end-to-end |
+| Blink before transition | Exit animation before shader fires | Remove exit tweens -- shader IS the exit |
+| Blink before transition | Transition duration < 0.3s | Increase to 0.5s |
+| Seeking backwards shows blank | Async capture race condition | Known bug in HyperShader browser mode. Forward seek usually works. For reliable scrubbing, download and use `npx hyperframes preview` locally |
+| Middle scene invisible | First shader anchor not shown | Add `tl.set("#sN", { opacity: 1 }, startTime)` for first anchor in each shader group |
+| Middle scene invisible | Non-anchor uses `visibility` instead of `autoAlpha` | Change to `tl.set("#sN", { autoAlpha: 1 }, start)` and `tl.set("#sN", { autoAlpha: 0 }, end)`. Shader blanket reset poisons opacity; `visibility` alone can't fix it. |
-Safari's WebKit applies stricter canvas-taint rules than Chrome. When a scene has a `` SVG element referenced as a `background-image: url("data:image/svg+xml...")` — a common grain/noise pattern — `html2canvas` produces a tainted canvas. Safari's WebGL then throws `SecurityError: The operation is insecure` at `gl.texImage2D()`, which has no framework opt-out (WebGL spec requires the check). Every shader transition falls through to the CSS-crossfade fallback; in Claude Design's cross-origin iframe sandbox this compounds with iframe throttling, and users see the whole piece play as hard cuts.
+### Deliver
-Empirically observed: skill-test8 in Safari + Claude Design = transitions work. skill-test-9 (identical framework, different grain implementation) in the same environment = zero shader transitions, all catch-handler fallbacks. The only structural difference was this:
+Provide: `index.html`, `preview.html`, `README.md`, and `DESIGN.md`.
-```css
-/* ✖ WRONG — SVG filter as background-image.
- Taints html2canvas's output canvas in Safari → breaks every shader
- transition in Safari + cross-origin iframes. Also measurably slower in
- WebKit than CSS gradients even when it does work. */
-.grain {
- position: absolute;
- inset: 0;
- pointer-events: none;
- opacity: 0.08;
- background-image: url("data:image/svg+xml;utf8,");
- mix-blend-mode: overlay;
-}
-```
-
-```css
-/* ✓ RIGHT — layered CSS radial-gradient dots. Same grain effect visually,
- pure CSS rendering, zero canvas taint, fast everywhere. */
-.grain {
- position: absolute;
- inset: 0;
- pointer-events: none;
- opacity: 0.18;
- background-image:
- radial-gradient(rgba(255, 255, 255, 0.08) 1px, transparent 1.2px),
- radial-gradient(rgba(0, 0, 0, 0.18) 1px, transparent 1.2px);
- background-size:
- 3px 3px,
- 5px 5px;
- background-position:
- 0 0,
- 1px 2px;
- mix-blend-mode: overlay;
-}
-```
-
-The same principle applies to other SVG-filter decoratives (paper fiber via `feTurbulence + feDisplacementMap`, CRT scanline overlays built from SVG patterns, etc.). In general, **avoid SVG filter data URLs in scene markup** — prefer layered CSS gradients, `backdrop-filter`, or solid-color overlays.
+The `preview.html` and `README.md` are already in the skeleton -- don't modify `preview.html`. Generate `DESIGN.md` from your `:root` custom properties as a reference document.
-**Escape hatch for unavoidable SVG effects.** If a scene genuinely needs an SVG filter (rare — usually a specific decorative that cannot be replicated in CSS), mark that element with `data-no-capture`. The shader's `captureScene()` already has logic to skip elements with this attribute — it won't enter the html2canvas clone pass, so it can't taint the output canvas. The element will still render live in the browser; it just won't appear in the shader transition textures (which for a grain overlay is usually invisible anyway, since the overlay is typically so subtle and repetitive that not seeing it mid-transition is imperceptible).
+In your final message, tell the user:
-```html
-
-
-```
-
-### Self-review — run this checklist before calling the build done
-
-Check every item with actual code, not assumptions.
-
-- [ ] Every scene has `class="scene clip"` + `data-start` + `data-duration` + `data-track-index`.
-- [ ] Non-first scenes have the correct inline style for the path in use. With HyperShader: `style="opacity:0;"` ONLY (no `visibility:hidden` — it breaks `captureIncomingScene` and produces content-fading-into-blank blinks during transitions). Without HyperShader: `style="visibility:hidden;"` ONLY (no `opacity:0` — nothing animates it back to 1).
-- [ ] Scene windows tile end-to-end with no gaps (scene-N's `data-duration` = next scene's `data-start` − this scene's `data-start`).
-- [ ] **Every scene has a `
` wrapper — not just scene-1.** Scan each scene's opening block and confirm the wrapper is present. Missing on any scene causes boxes/clipped elements during that scene's transition.
-- [ ] Animated content is INSIDE `.scene-content`; static decoratives are OUTSIDE.
-- [ ] **No scene is longer than 5 seconds** unless you can name the specific pacing reason (hero hold, cinematic push, silence beat, counter that needs ≥6s of runtime). Scenes of uniform length indicate you divided total duration by scene count instead of designing the rhythm.
-- [ ] **Every scene is long enough for its text to be read** — per the reading-time budget table in Step 3. 11–20 words needs ≥4s; 21–35 words needs ≥6s. The last readable text element in each scene finishes entering by the 50% mark of the scene so the viewer has the second half to actually read.
-- [ ] Shader transitions (if used) have the scene boundary strictly INSIDE the transition window — `transition.time < boundary < transition.time + duration`.
-- [ ] Zero `tl.set` / `tl.to` / `tl.from` / `tl.fromTo` on scene containers.
-- [ ] Every visible scene > 4s has a Breathe phase — at least one element in continuous motion, not just entrance + static.
-- [ ] Every element has a verb (from the verbs table) and an identifiable beat (build / breathe / resolve).
-- [ ] No banned fonts. No Inter, Roboto, Playfair, Syne. Check the full list.
-- [ ] No `Date.now()`, `Math.random()` unseeded, `repeat: -1`, `setInterval`, async timeline construction.
-- [ ] No `stagger: { from: "random" }` — GSAP's random is unseeded (Anti-pattern 2). Use `from: "start"`, `"center"`, `"end"`, or a grid origin instead.
-- [ ] No exit tweens except on the final scene. Grep every scene for `tl.to(..., { opacity: 0 })`, `tl.to(..., { autoAlpha: 0 })`, and `tl.to(..., { y: })` — these are Anti-pattern 1 and produce empty-scene captures.
-- [ ] No SVG filter data URLs as `background-image` (Anti-pattern 4). Grep for `data:image/svg+xml` in the CSS — if present, either replace with layered `radial-gradient`s (preferred) or add `data-no-capture` to the element. SVG filters taint html2canvas's canvas in Safari, killing every shader transition in Safari + cross-origin iframe environments.
-- [ ] Minimum font sizes: 60px+ headlines, 20px+ body, 16px+ labels. `font-variant-numeric: tabular-nums` on number columns.
-- [ ] No full-screen dark linear gradients (H.264 banding). Use radial or solid + localized glow.
-- [ ] `window.__timelines[""] = tl` is registered and the id matches `data-composition-id` on the root.
+1. **What you built** -- scene count, duration, visual identity summary, shader transitions used
+2. **What to do next** -- download the ZIP, run `npx hyperframes preview` locally to see the full composition with reliable playback
+3. **What to refine in Claude Code** -- be specific about which scenes need animation polish, where timing could be tighter, which mid-scene activities are basic and could be richer. Don't just say "refine in Claude Code" -- say "scene 4's counter animation could be smoother with a longer duration, and scene 6 would benefit from a breathing float on the logo."
+4. **Caveats** -- placeholder assets, unverified stats, elements inspired by a real brand
---
-## Step 5: Deliver
+## Section 6: Rules you cannot break
+
+The skeleton handles most structural rules. These are the runtime rules the skeleton can't enforce:
+
+### Determinism (non-negotiable)
+
+| Never | Use instead |
+| --------------------------------- | ---------------------------------------------- |
+| `Math.random()` | Seeded PRNG (only if you need randomness) |
+| `Date.now()`, `performance.now()` | Hard-coded timing or `tl.time()` in `onUpdate` |
+| `setInterval`, `setTimeout` | Timeline tweens + `onUpdate` |
+| `repeat: -1` | `repeat: Math.ceil(duration / cycle) - 1` |
+| `stagger: { from: "random" }` | `from: "start"`, `"center"`, `"end"` |
+| Async timeline construction | Synchronous at page load |
+
+### Media rules
+
+| Never | Use instead |
+| ------------------------------- | --------------------------- |
+| `video.play()`, `audio.play()` | Framework owns playback |
+| `