Emit an inverse-alpha background plate alongside the cutout in a single
inference pass. Same source RGB, alpha = 255 − mask. Dual-encoder pipeline
runs in parallel; both outputs share the same --quality preset.
This is a hole-cut plate (subject region transparent), not an inpainted
clean plate — composite something opaque under it to fill the hole.
Docs and skill cover when each is the right tool.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The model removes background from any video with a person — we tested
with avatars because they were convenient, but anyone can bring a
talking-head clip, presenter footage, vlog, etc. Replace avatar-specific
filenames (avatar.mp4 / brandon.mp4) with neutral subject.mp4 (or
presenter.mp4 in the text-behind-subject example) and rephrase
copy that read as if avatars were the only use case.
Touches docs/guides/remove-background.mdx, hyperframes-media SKILL.md,
and hyperframes/patterns.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skill (hyperframes-cli): three-pattern table (cutout-over-different-scene
vs over-its-own-source vs over-different-take) + the two non-obvious rules
(wrap video in non-timed div for opacity control, both videos data-start=0
for sync). Skill (hyperframes/patterns): worked text-behind-subject example.
Docs: --quality flag, compositing pitfalls section, quality preset table.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review on PR #619 caught two places that still pointed transcribe/tts
at hyperframes-cli — directly undercutting the description-trigger
goal of the split:
- skills/hyperframes/SKILL.md description ended with "For CLI commands
(init, lint, preview, render, transcribe, tts) see the
hyperframes-cli skill." Now splits the redirect: dev-loop commands
(init, lint, inspect, preview, render) → hyperframes-cli; asset
preprocessing (tts, transcribe, remove-background) →
hyperframes-media.
- packages/cli/src/templates/_shared/CLAUDE.md is the skills table
baked into every project bootstrapped by `hyperframes init`. Its
hyperframes-cli row still listed transcribe/tts. Trimmed to the
dev-loop commands and added a hyperframes-media row beside it, so
new projects pick up the correct mapping.
Also caught by greppping for stale skill lists:
- .codex-plugin/plugin.json longDescription bundled transcribe/tts
into "use the CLI for ...". Split into "use the CLI for the dev
loop (init/preview/render), preprocess assets
(tts/transcribe/remove-background)" so the Codex plugin store
surface matches reality.
Confirmed `npx hyperframes skills` shells out to `npx skills add
heygen-com/hyperframes --all` (packages/cli/src/commands/skills.ts),
so the skill list is read dynamically from the repo and picks up
hyperframes-media without code changes.
Code review found the new hyperframes-media skill was parallel
content with skills/hyperframes/references/tts.md and the "Whisper
Model Guide" section of transcript-guide.md — same voice table, same
.en-translates-non-English warning, same TTS→transcribe chain in
both places. Plus some scope creep in hyperframes-media (audio/video
HTML snippets that duplicate the canonical track docs in
hyperframes/SKILL.md:265+).
Consolidation:
- hyperframes-media is now the single source of truth for CLI
invocation, voice selection, multilingual phonemization, whisper
model selection, and the .en gotcha. Picked up the multilingual
prefix decoding from the deleted tts.md.
- skills/hyperframes/references/tts.md deleted; the bullet in
hyperframes/SKILL.md is removed (no replacement — agents land on
hyperframes-media via its own description).
- skills/hyperframes/references/transcript-guide.md keeps only the
caption-side concerns: input-format table, mandatory quality
check, cleaning JS, external-API import path, and the
"if no transcript exists" flow. The intro bash recipe and Whisper
Model Guide section both moved to hyperframes-media. Top of the
file now points to hyperframes-media for CLI/model details.
Other tightening in hyperframes-media:
- Dropped WHAT-narration filler and the inline <audio>/<video> HTML
snippets — they duplicate the canonical track-attribute docs in
hyperframes/SKILL.md.
- Added the `id` field (`w0`, `w1`, ...) to the transcript output
shape — the actual Word interface in
packages/cli/src/whisper/normalize.ts includes it (optional for
backwards compat), used by caption override logic.
- Compressed the TTS → Transcribe → Captions chain section.
Net: hyperframes-media 147 → 136 lines, transcript-guide.md 152 →
106 lines, tts.md gone (-75 lines).
Move tts/transcribe/remove-background guidance into a new
hyperframes-media sibling skill so the CLI skill stays focused on
the dev loop (init/lint/inspect/preview/render/doctor).
Two motivations:
1. Description bloat. The CLI skill listed every subcommand as a
trigger keyword, which made agents auto-load it for any mention
of audio, transcription, or backgrounds — even when the task
was just rendering a composition.
2. Body bloat. Voice tables, the .en-translates-non-English
whisper rule, and codec selection guidance all loaded on
every CLI invocation. With three preprocessing commands now
in the CLI (tts, transcribe, remove-background), this is only
going to grow.
The split keeps a single sibling (hyperframes-media), not three:
the commands share a workflow (preprocess asset → drop into
composition) and the same first-run-downloads-a-model pattern,
so they belong together. CLI skill now references hyperframes-media
from a one-paragraph "Asset Preprocessing" stub.
Doc references updated in README.md, CLAUDE.md,
docs/quickstart.mdx, and docs/guides/prompting.mdx.
Follow-up on the PR #603 review. The previous fix named both attributes
but didn't make their distinct shapes / roles obvious; a reader could
still wonder "are these two views of the same data?". Now the doc
opens with the shape contrast (array of declarations vs object of
values) and the section closes with a numbered precedence layering so
the merge order is unambiguous.
- compositions.md: replaced the bullet list with a shape-first
description ("JSON array of declarations" vs "JSON object keyed by
variable id"), an explicit "they aren't redundant" line, and a
numbered list of the three precedence layers (declared default →
host data-variable-values → CLI --variables).
- skills/hyperframes-cli/SKILL.md: highlighted the same shape contrast
inside the parametrized-renders paragraph (declarations array vs
values object).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
James pointed out (compositions.md:33, SKILL.md:121) that the prose
referenced `data-variable-values` while the example below showed
`data-composition-variables`, leaving readers to wonder if the two
names referred to the same thing. They don't: one declares, the other
overrides per-instance. Both are now named at first mention and the
declare-vs-override split is called out explicitly.
- packages/cli/src/docs/compositions.md: replaced the single intro
sentence with a two-bullet list ("data-composition-variables
declares, data-variable-values overrides per-instance") and a
follow-up explaining where the CLI fits in.
- skills/hyperframes-cli/SKILL.md: rewrote the parametrized-renders
paragraph so declaration (data-composition-variables) and override
(--variables) are distinct sentences, with the per-instance attribute
parenthetical for completeness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Distribution PR for the variables feature stack: tells agents how to
declare, read, and override variables across the four authoring
surfaces.
skills/hyperframes/SKILL.md:
- Added data-variable-values + data-composition-variables to the
data-attributes tables (host element + <html> root respectively).
- New "Variables (Parametrized Compositions)" section right after
"Composition Structure". Three-step pattern (declare / read /
override), full worked example with enum variable, sub-comp
per-instance pattern with two hosts sharing a source, and rules
of thumb (always provide defaults; read once, not in frame loops;
use --strict-variables in CI; type validation behavior).
skills/hyperframes-cli/SKILL.md:
- Added --variables, --variables-file, --strict-variables to the
render flag table.
- Short paragraph below the table explaining the parametrized-render
pattern with a forward reference to the hyperframes skill.
docs/packages/core.mdx:
- Added a code snippet showing getVariables<T>() inside a composition
and validateVariables/formatVariableValidationIssue for tooling.
packages/cli/src/docs/compositions.md (the in-CLI `npx hyperframes
docs compositions` content):
- Replaced the hand-rolled JSON.parse(host.dataset.variableValues)
pattern with the modern getVariables() pattern.
This is PR 4 of the 4-PR stack. The openai/plugins mirror is a
separate follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `hyperframes remove-background` — a local-AI subcommand that mattes a
video or image with the u2net_human_seg ONNX model and emits a transparent
WebM (VP9-alpha), ProRes 4444 .mov, or RGBA PNG. Drops directly into any
composition's <video> tag — no green screen, no API keys, no upload.
Auto-picks the fastest available execution provider via onnxruntime-node:
CoreML on Apple Silicon, CUDA when HYPERFRAMES_CUDA=1, CPU otherwise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Problem
I reproduced the selected open issue batch one by one and confirmed the reports were valid. The fixes all touch the CLI/runtime capture boundary, then the follow-up regression run exposed one over-broad runtime change in sub-composition host visibility and one CI-only baseline trap.
Closes#590, #589, #588, #587, #586, and #584.
## What this fixes
### CLI/runtime edge cases
- Makes the GSAP infinite-repeat lint rule ignore JavaScript comments, so literal `repeat:-1` text in comments is not flagged.
- Lets the compositions CLI inspect `<template>` content, count visual-only template descendants, estimate simple GSAP durations, and suppress root `data-start` warnings in sub-composition lint mode.
- Preserves runtime bootstrap scripts when body scripts are coalesced, and injects the runtime into a real `<head>` when source HTML has no head.
- Keeps #589 fixed by loading and rendering template-wrapped sub-composition content, while restoring host visibility to the shorter of the authored parent clip window and the child composition live timeline.
- Resolves snapshot/validate viewport size from root `data-width` / `data-height` instead of falling back to 1920x1080.
- Skips fully off-frame text boxes during contrast sampling and bounds-checks ring samples so contrast output no longer emits `null:1` / `NaN:1`.
- Marks muted videos as `data-has-audio="false"` in the core timing compiler, which fixes the same-src muted `<video>` + separate `<audio>` StaticGuard case.
- Keeps user-authored `hf-seek` listeners reachable during capture by preventing author scripts from being merged into the runtime bootstrap path.
### Shared helper cleanup
- Removes the stale producer-local timing compiler duplicate; producer compilation now consumes the core timing compiler.
- Centralizes HTML document helpers in core: fragment parsing, embedded runtime stripping, head/body script injection, and early-head injection.
- Centralizes the CLI layout/snapshot static HTML server.
- Adds browser-safe core subpath helpers for Lottie readiness and CLI screenshot clip calculation; Studio's Vite config keeps the screenshot clip helper self-contained so clean-checkout test startup does not value-import core `.ts` source.
- Replaces the engine parity-contract copy with a core re-export.
- De-duplicates render-job cleanup and Studio static file-serving callbacks.
### Regression hardening
- Replaces the embedded-runtime script stripping regex with a script-tag scanner that handles closing tags like `</script >`.
- Escapes inline script bodies before wrapping them in `<script>` tags, so authored `</script` and `<!--` text cannot break out of the injected wrapper script.
- Shares media-duration clamping between core and producer, with a 50 ms tolerance for ffprobe precision drift between local and CI media stacks.
- Pins the affected style fixture SFX durations in source so style-1 and style-9 compile deterministically.
- Restores the `vfr-screen-recording` video golden to the CI-stable baseline; the current CI failure showed the Linux render matches the old golden, while the locally refreshed macOS golden was the mismatch.
## Root cause
The CLI paths had accumulated assumptions that held for simple direct-root landscape compositions but not for current composition patterns: DOM queries did not enter template content, snapshot/validate used a fixed viewport, runtime and author scripts shared a coalescing bucket, and timing compilation treated every video as audio-bearing unless authors manually overrode it.
The style shard failures were not product regressions. Local and CI media probing disagreed on the short SFX clip duration by about 45 ms, and the compiler was clamping authored durations to the locally probed value. The shared clamp tolerance preserves explicit author/source durations for small probe precision differences while still clamping real overflows.
The vfr fast-shard failure was a bad baseline refresh: CI actual frames matched the old `vfr-screen-recording` baseline at 40+ dB PSNR, but mismatched the macOS-refreshed golden at ~18-22 dB. The fix is to keep the Docker/Linux-stable video golden and only retain the deterministic compiled snapshot change.
The sub-composition regression came from treating a host's authored parent window as the only visibility boundary. That made settled child overlays stay visible after their own live GSAP timeline ended. The corrected runtime behavior respects both contracts: parent clips still bound where the host can appear, and the child live timeline can end the host earlier.
## Verification
### Local checks
- `bunx oxfmt --check packages/core/src/runtime/init.ts packages/core/src/runtime/init.test.ts`
- `bunx oxlint packages/core/src/runtime/init.ts packages/core/src/runtime/init.test.ts`
- `bun run --cwd packages/core test src/runtime/init.test.ts`
- `bun run --cwd packages/cli test src/commands/compositions.test.ts src/utils/compositionViewport.test.ts`
- `bun run build:hyperframes-runtime`
- `bun run --cwd packages/producer test --keep-temp --sequential style-12-prod style-5-prod`
- `bun run --cwd packages/producer test --sequential vfr-screen-recording hdr-hlg-regression style-7-prod`
- `bun run --cwd packages/core test src/compiler/htmlCompiler.test.ts src/compiler/timingCompiler.test.ts src/index.test.ts`
- `bunx oxfmt --check packages/core/src/compiler/timingCompiler.ts packages/core/src/compiler/htmlCompiler.ts packages/core/src/compiler/htmlCompiler.test.ts packages/core/src/compiler/index.ts packages/core/src/index.ts packages/core/src/index.test.ts packages/producer/src/services/htmlCompiler.ts`
- `bunx oxlint packages/core/src/compiler/timingCompiler.ts packages/core/src/compiler/htmlCompiler.ts packages/core/src/compiler/htmlCompiler.test.ts packages/core/src/compiler/index.ts packages/core/src/index.ts packages/core/src/index.test.ts packages/producer/src/services/htmlCompiler.ts`
- `bun run --cwd packages/core typecheck`
- `bun run --cwd packages/producer typecheck`
- `bun run --cwd packages/producer test --sequential style-1-prod style-9-prod`
- `bun run --filter @hyperframes/studio test` with `packages/core/dist` temporarily hidden to simulate clean-checkout config loading
- `git diff --check`
### CI artifact checks
- Inspected failed run `25225854394` job `73969147096`: style-1 failed only on `click-sfx` `1.044898` vs `1` duration/end.
- Inspected failed run `25225854394` job `73969147061`: style-9 failed only on SFX `1.044898`-based duration/end mismatches.
- Inspected failed run `25225854394` job `73969147048`: `vfr-screen-recording` compilation/audio passed, visual failed after comparing against the macOS-refreshed golden.
- Compared the first 10 uploaded CI vfr failure frames against the restored old baseline; minimum PSNR was `40.444705`, above the fixture threshold of `28`.
### Repro checks
- `bun packages/cli/src/cli.ts lint /tmp/hf-590-repro` now passes without `gsap_infinite_repeat`.
- `bun packages/cli/src/cli.ts snapshot /tmp/hf-587-repro --at 0.5 --timeout 1000` now writes a 1080x1920 PNG.
- `bun packages/cli/src/cli.ts validate /tmp/hf-588-repro --timeout 500` no longer emits `null:1` / `NaN:1` contrast output.
- `bun packages/cli/src/cli.ts validate /tmp/hf-586-repro --timeout 500 --contrast false` no longer emits the muted-video StaticGuard contract error.
- `bun packages/cli/src/cli.ts compositions /tmp/hf-589-gsap-repro` now reports `foo 0.5s 1920x1080 1 element`.
- `bun packages/cli/src/cli.ts snapshot /tmp/hf-589-gsap-repro --at 0.25 --timeout 2000` captures the expected template-backed red frame.
- `bun packages/cli/src/cli.ts snapshot /tmp/hf-584-repro --at 0.5,1.5 --timeout 500` captures the expected post-seek green frame.
### Browser verification
- Refreshed the local side-by-side comparison page at `qa-artifacts/pr-591-video-compare/index.html`.
- Served the comparison page locally and used `agent-browser` to load `style-12-prod`, play both videos quickly to the failed window, pause, and inspect the side-by-side frame.
- Browser proof screenshot: `qa-artifacts/pr-591-video-compare/browser-proof/fixed-style12-labeled.png`.
- Browser proof recording: `qa-artifacts/pr-591-video-compare/browser-proof/fixed-style12.webm`.
- Earlier Studio proof artifacts remain local-only: `qa-artifacts/dedupe-refactor-preview.png`, `qa-artifacts/dedupe-refactor-preview-after-play.png`, `qa-artifacts/dedupe-refactor-preview.webm`.
## Notes
- Browser proof and CI diagnostic artifacts are intentionally local-only and not committed.
- Studio's Vite config intentionally keeps the thumbnail clip helper inline because Vite/Vitest config startup runs through Node's loader before package source `.ts` imports are transformed.
- The committed PR diff changes `vfr-screen-recording/output/compiled.html` but no longer changes `vfr-screen-recording/output/output.mp4` relative to `main`.
- I attempted a local `linux/amd64` Docker validation to mirror CI, but the local Docker build was blocked by Debian package download failures. The arm64 Docker image also cannot launch the x64 Puppeteer headless shell under OrbStack. The vfr baseline decision is therefore based on the uploaded CI artifact comparison above.
- I kept this validated issue batch in one PR because the fixes overlap the same CLI/runtime capture surfaces.
User feedback (jasonpurdy on X, https://x.com/jasonpurdy/status/2049985508701556855)
flagged that the remotion-to-hyperframes skill auto-triggered during an A/B
test of HyperFrames vs Remotion, producing a translated output instead of a
native HyperFrames composition. The user preferred the native version once he
disabled the skill.
The previous SKILL.md description listed four triggering conditions, three
of which were context-detection patterns (the user provides Remotion source,
pastes a Remotion entry point, links a Remotion repo). Agents could
interpret any of those as authoritative even when the user wasn't asking for
a migration.
Tighten the trigger gate so the skill only fires on an explicit migration
verb (port, convert, migrate, translate, rewrite as HyperFrames). Add
explicit NOT clauses for the common false-positive cases — including the
specific A/B-test case (the same video as my Remotion one — treat as a
fresh build). Default recommendation when uncertain: use the hyperframes
skill instead.
The body of the SKILL.md is unchanged — translation guidance is correct
once the gate is passed; this only tightens the gate itself.
## Problem
Users who want Tailwind utilities in a plain HyperFrames composition currently have to know which Tailwind browser script to add and where to place it. The first pass added `--tailwind`, but review caught three production-facing gaps: the CDN version was major-only, the insertion helper could silently no-op on compact HTML, and the render pipeline did not explicitly wait for Tailwind's async browser compilation before capturing frame 0.
There is also a version-specific agent risk: HyperFrames `init --tailwind` uses Tailwind v4.2 through `@tailwindcss/browser@4.2.4`, while `packages/studio` still uses Tailwind v3. Without a dedicated skill, agents can easily mix v3 `tailwind.config.js` / `@tailwind` patterns into v4 browser-runtime composition HTML.
## What this fixes
- Adds `hyperframes init --tailwind`.
- Pins the Tailwind browser runtime to `@tailwindcss/browser@4.2.4/dist/index.global.js` with SRI and `crossorigin="anonymous"`.
- Injects a `window.__tailwindReady` promise next to the browser runtime.
- Makes frame capture wait for `window.__tailwindReady` in both screenshot and BeginFrame capture modes before capturing frame 0.
- Inserts Tailwind support before `</head>` case-insensitively, including single-line/minified heads, and falls back to prepending when there is no head tag.
- Skips recursive Tailwind injection under `.git`, `dist`, and `node_modules`.
- Tracks whether init used Tailwind in the existing `init_template` telemetry event.
- Adds a first-party `/tailwind` skill for Tailwind v4.2 browser-runtime HyperFrames composition work.
- Updates README, docs, generated project agent files, CLI skill guidance, and plugin metadata so the Tailwind skill is discoverable.
- Documents the browser-runtime tradeoff and production/offline guidance.
## Root cause
`scaffoldProject()` copied the selected example and patched media placeholders, then immediately wrote project metadata and `package.json`. There was no optional post-copy step for framework-specific HTML support. The initial Tailwind post-copy step also treated the browser runtime like a static script, but Tailwind compiles utilities asynchronously after scanning the DOM, so the capture engine needed an explicit readiness contract.
On the agent side, the repo exposed HyperFrames, CLI, GSAP, registry, and runtime adapter skills, but had no Tailwind-specific instruction to separate the v4 browser-runtime composition path from Studio's v3 internal setup.
## Verification
### Local checks
- `bunx vitest run packages/cli/src/commands/init.test.ts`
- `bun run --filter @hyperframes/cli test src/commands/init.test.ts`
- `bun run --filter @hyperframes/cli typecheck`
- `bun run --filter @hyperframes/engine typecheck`
- `bun run lint:skills`
- `bun run lint`
- `npx skills add . --list` showed 12 local skills, including `tailwind`.
- `bunx oxfmt --check packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts docs/packages/cli.mdx`
- `bunx oxfmt --check README.md docs/quickstart.mdx docs/packages/cli.mdx CLAUDE.md packages/cli/src/templates/_shared/CLAUDE.md packages/cli/src/templates/_shared/AGENTS.md skills/hyperframes-cli/SKILL.md skills/tailwind/SKILL.md .codex-plugin/plugin.json .cursor-plugin/plugin.json`
- `bunx oxlint packages/cli/src/commands/init.ts packages/cli/src/commands/init.test.ts packages/cli/src/telemetry/events.ts packages/engine/src/services/frameCapture.ts`
- `git diff --check`
- Lefthook pre-commit: lint/format/typecheck for code commit; format for docs/skill commit
- Lefthook commit-msg: commitlint
Generated-project render proof at `/tmp/hf-tailwind-render-proof`:
- `bun packages/cli/src/cli.ts init /tmp/hf-tailwind-render-proof --example blank --tailwind --non-interactive --skip-skills`
- Added a temporary Tailwind-only card using `flex`, `h-full`, `w-full`, `items-center`, `justify-center`, `bg-slate-950`, `rounded-3xl`, `bg-white`, `px-20`, `py-12`, `text-8xl`, `font-black`, `text-black`, and `shadow-2xl`.
- `bun packages/cli/src/cli.ts lint /tmp/hf-tailwind-render-proof` → 0 errors, 0 warnings.
- `bun packages/cli/src/cli.ts validate /tmp/hf-tailwind-render-proof` → 0 errors, 0 regular warnings; the temp proof still reports validator contrast warnings even though the rendered/browser pixels show black text on white background.
- `bun packages/cli/src/cli.ts render /tmp/hf-tailwind-render-proof --workers 1 --fps 24 --quality draft --output /tmp/hf-tailwind-render-proof-artifacts/output.mp4`
- Render compiler inlined both GSAP and `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.2.4/dist/index.global.js`.
- `ffprobe -v error -select_streams v:0 -show_entries stream=codec_name,width,height,r_frame_rate,duration -of default=noprint_wrappers=1 /tmp/hf-tailwind-render-proof-artifacts/output.mp4` → H.264, 1920x1080, 24fps, 10s.
- Extracted frame-0 proof: `/tmp/hf-tailwind-render-proof-artifacts/frame-000.png`.
### Browser verification
- Started Studio preview for `/tmp/hf-tailwind-render-proof`.
- Used `agent-browser` to open `http://localhost:5194`.
- Verified the Tailwind-styled composition rendered in Studio preview.
- Captured screenshot: `/tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.png`.
- Captured agent-browser-driven recording: `/tmp/hf-tailwind-render-proof-artifacts/browser/tailwind-preview.webm`.
- Served the PR worktree locally and used `agent-browser` to open the new Tailwind skill proof page.
- Verified the browser-visible skill content includes `@tailwindcss/browser@4.2.4`.
- Captured screenshot: `/Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.png`.
- Captured agent-browser-driven recording: `/Users/miguel07code/.codex/worktrees/pr-577-tailwind-comments/tmp/agent-browser-proof/tailwind-skill.webm`.
## Notes
- This still intentionally uses Tailwind's browser runtime rather than adding a generated Tailwind build pipeline. That keeps `hyperframes init --tailwind` small and compatible with the current no-install generated project workflow.
- The `/tailwind` skill cites official Tailwind v4 docs plus community skill references, but its instructions are HyperFrames-specific and tuned for the pinned v4.2 browser runtime.
- Browser proof artifacts are local-only under `/tmp/hf-tailwind-render-proof-artifacts/` and `tmp/agent-browser-proof/` and intentionally not committed.
The leaf PR. Replaces the placeholder SKILL.md from PR 1 with the real
5-step workflow that loads the per-topic references on demand
(skill-creator's progressive-disclosure pattern), and adds a top-level
orchestrator that runs every tier and reports a pass/fail summary.
SKILL.md changes:
- Frontmatter unchanged from PR 1 (already covers the trigger phrases
and out-of-scope cases)
- Body rewritten as a 5-step workflow:
1. Lint (load escape-hatch.md if blockers)
2. Plan (load api-map.md, then per-topic references on demand)
3. Generate (HF index.html with paused GSAP timeline)
4. Validate (render_diff.sh against per-tier threshold)
5. Document gaps (TRANSLATION_NOTES.md if needed)
- Includes a "Source contains -> Load reference" table so the agent
only loads the references the source actually needs
- Documents the validated baseline numbers (T1 0.974, T2 0.985,
T3 0.953, T4 8/8) so reviewers can reproduce
- Calls out the critical Remotion encoder config (PNG + BT.709) that
avoids the ~0.05 SSIM hit from yuvj420p vs yuv420p
Orchestrator (assets/test-corpus/run.sh):
- Iterates tier-1-* through tier-4-* directories
- T1-T3: setup -> lint -> npm install (lazy) -> render Remotion ->
render HF -> SSIM diff at the fixture's expected threshold ->
generate strip on failure
- T4: validate.sh (lint-only)
- Emits run-report.json with per-tier pass/fail and aggregate counts
- Accepts a single-tier argument for fast iteration: ./run.sh tier-1-title-card
Validated end-to-end on a clean checkout:
▶ tier-1-title-card → mean SSIM 0.9739 (≥ 0.95) ✓
▶ tier-2-multi-scene → mean SSIM 0.985292 (≥ 0.95) ✓
▶ tier-3-data-driven → mean SSIM 0.952941 (≥ 0.9) ✓
▶ tier-4-escape-hatch → 8/8 cases ✓
passed 4/4, failed 0, skipped 0
Closes the 7-PR stack: scaffold, eval harness, 4 tiers of corpus,
references, and now the SKILL.md body that ties everything together.
Adds 11 progressively-disclosed reference files that the skill loads on
demand during translation. Total ~1500 LOC, every file under 200 lines
(skill-creator's progressive-disclosure budget).
api-map.md the comprehensive Remotion -> HF translation table
(the index; loaded at start of translation)
timing.md interpolate, spring (validated configs), easing,
count-up, stagger
sequencing.md Sequence, Series, Loop, Freeze, AbsoluteFill,
Composition root
media.md Audio, Video, Img, IFrame, OffthreadVideo,
staticFile, asset paths
transitions.md @remotion/transitions presentations -> manual GSAP
crossfades or HF shader-transitions
lottie.md @remotion/lottie -> HF lottie adapter (incl. AE
feature limitations note)
fonts.md Google Fonts loading, local @font-face, system
fallback noise floor
parameters.md Zod schemas, defaultProps, sync vs async
calculateMetadata
escape-hatch.md when to bow out + the runtime interop pattern
from PR #214
limitations.md known caveat patterns (volume ramps, Loop with
state, custom presentations, code-split components)
eval.md how to run the validation harness, threshold rule
of thumb, what the noise floor looks like
The references are evidence-driven rather than speculative: every spring
config, easing curve, and SSIM threshold is documented from the
validated T1/T2/T3 calibration runs (mean 0.974 / 0.985 / 0.953). The
escape-hatch boundaries match the lint blockers in PR 2 and the T4
fixtures in PR 5.
Replaces the placeholder .gitkeep from PR 1.
Adds the escape-hatch tier — lint-only fixtures that test the skill's
ability to refuse translation cleanly when it sees patterns that don't map
to HF's seek-driven model.
Cases (8 total):
01-use-state.tsx blocker: r2hf/use-state
02-use-effect-deps.tsx blocker: r2hf/use-effect-deps (multi-line body
with internal commas — regression target for
the regex bug fix in PR 2)
03-async-metadata.tsx blocker: r2hf/async-metadata
04-third-party-react.tsx blocker: r2hf/third-party-react-ui (@mui/material)
05-lambda-config.tsx blocker: r2hf/lambda-import
06-warnings-only.tsx warnings: delayRender / useCallback / useMemo
(no blockers — translates after dropping wrappers)
07-custom-hook.tsx warning: r2hf/custom-hook (pure useFadeIn)
08-mixed.tsx multiple blockers + warnings (aggregate test)
Each case documents:
- The Remotion pattern it demonstrates
- Why it's a blocker / warning / info
- What the skill should do (refuse / drop-and-translate / translate-as-is)
Validation harness (validate.sh):
Runs lint_source.py against each case, asserts:
- Each expected blocker rule fires with severity="blocker"
- Each expected warning rule fires with severity="warning"
- lint_source.py exit code is 1 when blockers expected, 0 otherwise
T4 has no renders to diff. The skill is graded on lint correctness — that's
the gate that decides whether to translate or recommend the runtime interop
pattern from PR #214.
Result: 8/8 cases pass.
Adds the data-driven tier — a purpose-built fixture (option 2 from the
stack discussion, not a port of PR #214's examples/remotion-full/) that
exercises the realistic shape of a production Remotion composition
without using the runtime adapter.
Stargazed.tsx (10s @ 30fps, 1280x720):
Sequence 0-3s TitleScene (title + subtitle)
Sequence 3-7s StatsScene (3 reused StatCards staggered 12 frames apart)
Sequence 7-10s OutroScene (UnderlinedText with scaleX-from-left underline)
Composition shape exercises:
- <Composition schema={z.object({...})} defaultProps={...} />
- nested array prop (stats[]) materialized as repeated HTML
- custom React subcomponents (StatCard, AnimatedNumber, UnderlinedText)
reused with different props
- per-instance delay via prop (delayInFrames -> GSAP timeline offset)
- frame-driven count-up (AnimatedNumber, manual cubic ease-out)
- two different spring configs in the same composition
(damping:12 -> back.out(1.4), damping:14 -> back.out(1.2))
- useCurrentFrame, useVideoConfig
Translation choices documented in README.md and expected.json:
- Zod props -> data-* on root #stage div
- Custom subcomponents inline as repeated HTML using prop interface
as the template
- AnimatedNumber's frame-driven count-up -> GSAP onUpdate tween on a
{ v: 0 } counter object, ease power3.out
- Two different spring configs -> two different back.out overshoots
(1.4 vs 1.2 approximates the damping difference)
- delayInFrames={i * 12} -> GSAP offset (i * 0.4)s
Validated end-to-end: rendered Remotion baseline + HF translation, ran
scripts/render_diff.sh.
measured mean SSIM 0.953
measured min SSIM 0.927
measured p05 SSIM 0.938
threshold 0.90 (~0.04 below p05)
The wider gap vs T1/T2 reflects T3's bigger approximation budget
(2 spring instances + count-up timing + font fallback on multiple text
sizes). Mean SSIM below 0.90 = structural mismatch (wrong durations,
wrong stagger, missing prop wiring), not approximation drift.
Same Remotion config as PR 3: setVideoImageFormat("png") +
setColorSpace("bt709") to match HF's yuv420p output.
Lint: 9 files scanned, 0 blockers / 0 warnings / 0 infos.
oxlint, oxfmt, typecheck all pass.
The fixture is not yet wired into CI; render + diff is documented in
README.md and runs by hand via the harness from PR 2. PR 7's orchestrator
will wire all four tiers into a CI eval run.
Adds the first two test fixtures the skill is graded against. Each fixture
ships:
- remotion-src/ full Remotion project (package.json, src/, remotion.config.ts, tsconfig.json)
- hf-src/ hand-translated HyperFrames composition (index.html)
- expected.json tier metadata + SSIM threshold + translation notes + measured validation
- README.md human walk-through of the translation choices
- setup.sh (T2 only) generates binary assets (PNG, WAV) via ffmpeg
T1 — title-card-fade
- 3 s @ 30 fps, 1280x720
- Single AbsoluteFill, single useCurrentFrame interpolate
with multi-segment input [0,15,75,90] -> [0,1,1,0]
- Validated mean SSIM 0.974, threshold 0.95
(~0.025 gap from font-fallback divergence between Remotion's bundled
Chromium and HF's chrome-headless-shell)
T2 — title-image-outro
- 6 s @ 30 fps, 1280x720, three Sequences (TitleScene, ImageScene, OutroScene)
- Exercises spring, interpolate, Audio, Img, staticFile
- Spring -> GSAP back.out(1.4) translation
- Validated mean SSIM 0.985, threshold 0.95
(translation came out cleaner than predicted; spring->back.out drift was
smaller than the ~0.05 budget I'd expected)
- setup.sh generates a 200x200 blue PNG and a 6 s silent WAV via ffmpeg
so binaries stay out of the repo
Calibration done end-to-end: rendered Remotion baseline + HF translation,
ran scripts/render_diff.sh, set thresholds ~0.02 below measured p05.
Critical Remotion config: setVideoImageFormat("png") + setColorSpace("bt709").
The default JPEG output writes yuvj420p (full-range) which costs ~0.05 SSIM
vs HF's yuv420p (limited-range). Both fixtures' remotion.config.ts encode
this so render_diff.sh measures translation fidelity, not encoder differences.
Both fixtures lint clean (0 blockers via scripts/lint_source.py).
T2 staticFile() references correctly flagged as info-level findings.
The fixtures are not yet wired into CI — that comes with PR 7's orchestrator.
For now, render and eval are documented in each README and run by hand.
Adds the deterministic eval primitives the skill calls into:
scripts/render_diff.sh SSIM diff between two MP4s, JSON summary, configurable threshold
scripts/frame_strip.sh side-by-side comparison strip for visual debugging
scripts/lint_source.py pre-translation lint over Remotion source — blocks/warnings/infos
The harness is decoupled from the render pipeline: it accepts paths to
already-rendered MP4s. The skill orchestrator (PR 7) drives both renders
and feeds the outputs in. This keeps the harness usable in CI, in
sandboxes, and on any machine that has ffmpeg without needing the full
Remotion + HyperFrames toolchain.
Lint catches the patterns from the skill's out-of-scope list:
- useState / useReducer (state-machine driven animation)
- useEffect with deps (side effects)
- async calculateMetadata (Promise-returning composition metadata)
- @remotion/lambda imports
- third-party React UI libraries (MUI, Chakra, Mantine, antd, shadcn, Radix, NextUI)
- delayRender / useCallback / useMemo (warnings)
- staticFile / interpolateColors (info — translatable but flagged)
Smoke test (scripts/tests/smoke.sh) exercises all three scripts against
synthetic inputs: identical ffmpeg testsrc videos pass at threshold 0.99,
different ffmpeg testsrc videos fail at 0.99, frame_strip produces a
strip.png, lint produces 0 blockers on a clean fixture and >=3 blockers
on a fixture that uses useState + useEffect + MUI + async metadata.
Validated locally: smoke.sh exits 0.
Adds the directory + SKILL.md frontmatter for a new skill that translates
Remotion (React) compositions to HyperFrames (HTML+GSAP). This is the
foundation PR; subsequent PRs in the stack add the eval harness, test
corpus, translation references, and finally the SKILL.md body.
The frontmatter description enumerates trigger phrases and explicit
out-of-scope cases (useState/useEffect, async metadata, @remotion/lambda)
so the skill bows out cleanly when a Remotion composition isn't a clean
translation target — those should use the runtime interop pattern from
PR #214 instead.
Validated with skill-creator's package_skill.py.
* fix(lint): remove root_composition_missing_data_duration
Lint cannot statically observe the runtime's true Infinity-emission
condition: it requires a finite GSAP timeline duration AND a finite
media/sub-comp window AND timeline > floor + 1, none of which are
visible to the static linter. The looping shapes that drive the
condition are already covered by `gsap_infinite_repeat` and
`gsap_repeat_ceil_overshoot` (both from #243), which point at the
real authoring mistake — flagging the missing duration separately
was a noisy proxy for the same signal.
Per #490 review discussion, deprecate the static rule and let those
two GSAP rules carry the pre-render coverage. If perfect precision
on `durationInFrames = Infinity` is needed, that belongs on the
runtime/render path where `shouldEmitNonDeterministicInf` is
actually known.
Add regression tests pinning the removal: a docs-compliant root
without `data-duration` no longer warns, and the canonical
loop-inflated shape now surfaces only via `gsap_infinite_repeat`
instead of two duplicate findings.
* docs(skills): update step-6 build checklist after rule removal
Drops the `root_composition_missing_data_duration` reference now that
the rule is gone. Keeps the authoring recommendation (and explains the
runtime Infinity case) but points authors at the GSAP rules that
actually carry the lint signal: `gsap_infinite_repeat` and
`gsap_repeat_ceil_overshoot`.
Per review feedback on PR #364: the 'hero vanishes' failure has two
mechanisms. Primary: second tween's immediateRender overwrites the first
at construction time. Secondary: tl.from() resets to its declared from-
state when seeked past timeline end, which the capture engine triggers.
Both are now named so the rule has precise rationale, not just a pattern
to avoid.
Ref: https://github.com/heygen-com/hyperframes/pull/364#pullrequestreview-4167523103
Add dynamic resolution to shader transitions and update Skeleton A
to use shaders on vertical compositions.
shader-transitions changes:
- webgl.ts: read dimensions from params instead of hardcoded constants
- capture.ts: accept width/height for html2canvas
- hyper-shader.ts: read data-width/data-height from composition root
skill + docs changes:
- Skeleton A now has 1 shader at hero reveal (s3→s4 midpoint)
- Removed "no shaders on vertical" limitation from docs
- Updated claude-design.mdx known limitations section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the rule-heavy Claude Design skill with a template-first
approach. Instead of teaching all rules from scratch (863 lines),
provide pre-valid skeletons where structural rules are embedded.
Claude Design fills in palette, content, and animations.
Key changes:
- Pre-valid HTML skeletons for social reel (vertical), launch teaser,
product explainer, and cinematic title
- Mixed transitions: 2-3 shader transitions at key moments, rest
hard cuts (matches professional video practice)
- autoAlpha toggles for non-anchor scenes (fixes invisible middle
scenes caused by HyperShader's blanket opacity reset)
- Explicit first-anchor opacity fix (HyperShader browser mode never
auto-shows the first anchor scene)
- No shaders on vertical (1080x1920) — WebGL canvas is hardcoded
to 1920x1080 in webgl.ts
- Inline animation patterns (counter, stroke draw, stagger, float,
bar chart, orbit, highlight sweep, safe CSS grain)
- Claude Design → Claude Code handoff workflow documented in
README template and delivery step
- Troubleshooting table for black preview, invisible scenes, seeking
- Self-review checklist split: structural validity, brand accuracy,
animation baseline
- Skill description updated per review feedback on PR #353 to
disambiguate from the hyperframes skill on cross-surface routing
Bugs fixed by this rewrite (confirmed across demov4-1 through
demov4-6 and demov5-1 through demov5-5):
- First shader anchor invisible in every composition
- Non-anchor scenes killed by blanket querySelectorAll(".scene")
opacity reset
- visibility toggles insufficient (opacity:0 persists after reset)
- Shaders on vertical compositions produce distorted transitions
## Summary
- add a GitHub-hosted `claude-design-hyperframes` skill entry point that tells Claude Design to fetch the upstream HyperFrames skills tree
- add a dedicated Claude Design docs guide and link it from quickstart, prompting, and the README
- fix `@hyperframes/player` CDN docs to show a working ESM include and the explicit global-build fallback
## Verification
- `bunx oxfmt --check README.md docs/docs.json docs/guides/prompting.mdx docs/packages/player.mdx docs/quickstart.mdx packages/player/README.md docs/guides/claude-design.mdx skills/claude-design-hyperframes/SKILL.md`
- `bun run lint:skills`
- `bunx mintlify broken-links`
- browser-engine screenshots captured with Playwright CLI for the changed docs/source surfaces:
- `/tmp/hyperframes-pr-artifacts/claude-design-guide-source.png`
- `/tmp/hyperframes-pr-artifacts/player-docs-source.png`
## Notes
- `mintlify dev`, `mintlify validate`, and `mintlify export` stalled in this environment during preview/bootstrap, so I used the broken-links check plus screenshot-based browser fallback instead of claiming a full rendered-site pass.
- The GitHub entry-point setup reflects current Claude Design behavior discussed in the task: point Claude Design at the repo-hosted skill URL rather than a ZIP upload flow.
Add five animation-authoring rules to step-6-build.md that the linter
cannot catch but that silently ship broken output. Surfaced from two
independent builds (2026-04-20) where compositions passed lint and
still rendered elements invisible / unscrubbing.
Rules added:
- No iframes for captured content (don't scrub)
- Never stack transform tweens on one element (entrance + Ken Burns
on same img silently kills it)
- Prefer tl.fromTo() over tl.from() inside .clip scenes
(immediateRender interacts badly with scene boundaries)
- Ambient pulses must attach to seekable tl, not standalone gsap.to()
- Generalize the caption hard-kill rule to every scene-boundary exit
* feat(cli): add --lang and auto-infer phonemizer locale from voice prefix
`hyperframes tts` was calling Kokoro's `model.create(text, voice=, speed=)`
with no language argument, so Kokoro's default phonemizer (en-us) was
applied regardless of the voice selected. Picking `ef_dora` or `jf_alpha`
and feeding it Spanish or Japanese text produced English-phonemized
output.
Closes#349.
- `manager.ts`: add `SUPPORTED_LANGS`, `inferLangFromVoiceId`, and
`isSupportedLang`. Attach a `defaultLang` field to every bundled voice
and expand the bundled list with `ef_dora`, `ff_siwis`, `jf_alpha`,
`zf_xiaobei` so `--list` surfaces multilingual options.
- `synthesize.ts`: accept optional `lang: SupportedLang` in
`SynthesizeOptions`, forward it to the Python worker as `argv[7]`.
The worker introspects `Kokoro.create`'s signature and only passes
`lang=` when the installed kokoro-onnx version supports it. Returned
metadata now includes `lang` and `langApplied` so callers can detect
silent no-ops. Bump the cached script filename to `synth-v2.py` so
existing installs pick up the new script automatically.
- `commands/tts.ts`: add `--lang, -l` with validation against
`SUPPORTED_LANGS`. Resolution order is explicit `--lang` > inferred
from voice prefix > `en-us`. When explicit lang disagrees with the
voice-implied lang (legitimate for stylized accents), emit a
dim-level hint; suppress under `--json`. When kokoro-onnx silently
ignores the kwarg, log that too. Update `--list` with a new
"Lang code" column and add multilingual examples.
- Tests: new `manager.test.ts` covering every supported prefix, the
unknown-prefix fallback, case-insensitivity, `isSupportedLang`
validation, and a regression guard that every bundled voice has a
valid `defaultLang` matching its ID.
- Docs: `docs/packages/cli.mdx` and `skills/hyperframes/references/tts.md`
updated with the flag, examples, the espeak-ng dependency note for
non-English phonemization, and the voice-prefix → lang table.
Backward compatibility:
- English voices (a*/b* prefixes) continue to phonemize as en-us / en-gb
— no change.
- Non-English voices now phonemize correctly by default (bug fix, not a
regression).
- Older kokoro-onnx versions that don't know the `lang` kwarg keep
working via signature introspection; the CLI logs a dim note if
`--lang` was requested but ignored.
Verification:
- `bun --cwd packages/cli test` — 128 tests pass (incl. 17 new).
- `bunx oxlint` and `bunx oxfmt --check` clean on changed files.
- `bun run build` succeeds.
- `npx tsx packages/cli/src/cli.ts tts --help` / `--list` render cleanly;
invalid `--lang` produces a clean error with the valid-codes list.
* refactor(cli): simplify tts --lang implementation
Post-review cleanup on #351. Net -21 lines.
- Drop `defaultLang` field + `makeVoice()` helper from VoiceInfo —
compute via `inferLangFromVoiceId(v.id)` at read time in listVoices.
The only reader was the --list table; caching the derived value on
every voice added a self-consistency invariant we had to test.
- Drop redundant `lang` field from SynthesizeResult — caller already
knows the requested lang since it passed it in; only `langApplied`
carries information the caller can't derive.
- Use `errorBox` for --lang validation to match the house style in
render.ts (other validation errors already use errorBox).
- Reuse existing `langList` module constant in the validation error
instead of re-joining SUPPORTED_LANGS.
- Inline `DEFAULT_LANG` — used once in inferLangFromVoiceId.
- Trim WHAT-restating comments and the duplicate prefix-enumeration
JSDoc on inferLangFromVoiceId (VOICE_PREFIX_LANG already carries
per-row comments).
- Clean up orphaned `synth*.py` files in ~/.cache/hyperframes/tts
when writing the current versioned script, so repeated upgrades
don't leak files.
- Drop the `EN-US` case-sensitive-rejection test assertion — the CLI
lowercases input before validation, so accepting mixed case is a
feature, not a bug.
Tests: 16/16 in `manager.test.ts`, 127/127 full CLI suite pass.
Lint + format + typecheck clean.
* fix(skill): all file paths use capture/ subfolder prefix
Step-1 was updated to capture into <project-dir>/capture/ but all
file-read instructions across steps 1-6 still used bare paths
(extracted/tokens.json, assets/hero.png). Agents following the skill
looked for files in wrong locations, producing broken compositions
with 404'd images and fonts.
- step-1: all 12+ file-read paths prefixed with capture/
- step-2: tokens.json and asset-descriptions.md paths fixed
- step-3: visible-text.txt path fixed
- step-4: asset-descriptions.md, asset examples, directory tree expanded
to show capture/ children (screenshots/, assets/, extracted/)
- step-5: minor cleanup
- step-6: asset-descriptions.md path fixed, composition examples already
correct (../capture/assets/)
- techniques.md: Lottie, video, font paths all prefixed
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skill): address Copilot review — font comment path, add videos/ to tree
- techniques.md: comment says ../capture/assets/fonts/ (matches composition context)
- step-4 file tree: add videos/ under capture/assets/
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Preview is the delivery — scrub through it, iterate on tweaks, only render
once the user explicitly asks ("render it", "make the final", "I'm happy").
Rendering takes minutes per pass and is wasted work if the user wants any
changes after seeing it.
- step-7-validate: rename Render section to "Render (on-demand only)" and
make the trigger criteria explicit
- SKILL.md: step-7 summary updated to "Deliver the preview to the user
first — only render to MP4 on explicit request"
Made-with: Cursor
Adds decision guidance and conventions the 8-site regression test exposed as
blind spots — agents had the capability but never reached for it.
step-1-capture:
- Clarify capture goes into <project-dir>/capture/ so capture artifacts stay
isolated from later build files (SCRIPT/STORYBOARD/DESIGN/compositions/)
- 7/8 regression tests already did this; codify as the convention
step-4-storyboard:
- Add "When to pick which" decision table for shader vs CSS vs hard cut
transitions. Shader transitions were available but used in 0/8 tests —
every test defaulted to CSS. The table frames shaders as "reveals, reaction
shots, brand moments" vs CSS as "connective tissue"
- Update technique count (10 → 11)
step-6-build:
- Mid-scene activity table gets a new row for audio-reactive logo/CTA
animation (bass pulse, treble glow). Audio-reactive was used in 0/8 tests
despite narration being present in all of them
step-7-validate:
- Snapshot section: explicit "use hyperframes snapshot, don't roll custom"
with the default naming pattern spelled out. Stripe's run used custom
ffmpeg naming (beat-6-cta-at-20.5s.png) instead of frame-XX-at-Ys.png
- New render section: require --output renders/<project>.mp4 so final MP4s
have predictable names. Without this, 7/8 tests produced wildly different
filenames (preview.mp4, cal_2026-04-19_20-29-21.mp4, basecamp.mp4, etc.)
techniques.md:
- New technique #11: Audio-Reactive Animation. Covers the sampling pattern
(per-frame tl.call, not single tween), when to use (music/dramatic VO
videos), intensity ranges (3-5% for text/logos, 10-30% for backgrounds),
and anti-patterns (equalizer bars, waveforms, strobing). Cross-references
skills/hyperframes/references/audio-reactive.md for the full API
Made-with: Cursor
Capture pipeline:
- agentPromptGenerator now writes AGENTS.md + CLAUDE.md (drop legacy
.cursorrules), matching the dual-file convention already used by the
_shared templates in hyperframes init. AGENTS.md is picked up natively by
Cursor, Codex, Gemini CLI, Windsurf, Aider, and Jules; CLAUDE.md covers
Claude Code. Both files share the same content — a capture data inventory
that points agents to the website-to-hyperframes skill.
website-to-hyperframes skill refinements (derived from 8-site regression test):
- Drop slash-command phrasing throughout SKILL.md and step-6-build.md so the
skill works identically across Claude Code (slash), Cursor (auto-discover
by description), and other agents.
- Remove stale HANDOFF.md references from SKILL.md step-7 summary and
reference table — matches the intent of the prior step-7 cleanup.
- step-5-vo: specify narration.txt filename convention (pronunciation-
substituted spoken text; distinct from SCRIPT.md the creative doc).
- step-6 self-review adds three rules derived from actual lint warnings
observed across the 8 regression runs:
- Every <template> root needs data-start + data-duration (catches
root_composition_missing_data_start/duration, seen in 4/8 runs).
- Caption exits need a hard tl.set kill after tl.to(opacity:0), or
per-word karaoke tweens can leave captions stuck on screen
(caption_exit_missing_hard_kill).
- No duplicate media nodes with identical src + start + duration, or
the compiler discovers them twice (duplicate_media_discovery_risk).
Housekeeping:
- .gitignore: add cursor-tests/, basecamp-video/, projects/, videos/ —
local regression-test scratch dirs that should never be committed.
- Remove two broken symlinks from .claude/skills/ that pointed to paths
which never existed in the repo (.claude/skills/ is already gitignored).
Made-with: Cursor
Capture improvements:
- Font weights via document.fonts API + DOM sampling (variable font detection)
- Section background-image extraction (no more false #FFFFFF on hero photos)
- Detected libraries surfaced in CLAUDE.md brand summary
- Structured visible-text.txt with [tag] prefixes, cookie/nav noise filtered
- tokens.json cleaned: removed images/paragraphs/icons (duplicated elsewhere),
filtered sections to heading-only, trimmed cssVariables to design-relevant
- Removed redundant scroll pass in htmlExtractor (2-5s faster per capture)
- Font cap at 20 families, Placeholder/Fallback fonts filtered
CLAUDE.md rewrite:
- Removed prescriptive tone ("use exact strings" → "rephrase freely")
- Removed fluff sections (How to Create, DESIGN.md warning, Example Prompts,
Source Patterns)
- asset-descriptions.md promoted to first data row
- Removed assets-catalog.json from inventory
Skill fixes:
- Dead shader refs → point to packages/shader-transitions/README.md
- Google Fonts import in techniques.md → local @font-face placeholder
- Added Stripe DESIGN.md as light-brand example
- Removed HANDOFF.md generation from step-7
- Updated step-1 for new font weight + visible-text formats