hyperframes-video — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited hyperframes-video (Agent Skill) and scored it 75/100 (yellow). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 6 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 6 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Turn a PR into a rendered short-form promo video with a hook, code moments, and CTA, using HyperFrames. Iterate with live preview via npx hyperframes preview, then render video.mp4 and poster.jpg for X/LinkedIn/social.
Unlike /video-script (which produces a textual script), this skill produces the actual video file. Counterpart to the remotion-video skill — same workflow shape, same storytelling rules, but built on the HyperFrames stack instead of Remotion.
Invoke when the user says: "make a video for this PR", "hyperframes video", "render a promo video", "video for X/LinkedIn", or when selected from /marketing-pipeline.
This skill carries only the promo-video-from-PR logic: input resolution, narrative planning, motif derivation, hook enforcement, scene-plan auditing, render-time gates. The mechanics of authoring HyperFrames compositions, running the CLI, animating with GSAP, and installing registry components are owned by separate skills you should invoke when their topic comes up:
npx hyperframes <command> (init, lint, inspect, preview, render, transcribe, tts, doctor, browser, info, upgrade, compositions, docs, benchmark).hyperframes add.When this skill needs to do something covered by one of those skills, defer to that skill's instructions rather than re-deriving them here.
Resolve input
→ Phase 1: Discovery
→ Phase 2: Configuration
→ Phase 3: Narrative planning
→ Phase 4: Scaffold
→ Phase 5: First draft + iterate
→ Phase 6: Render
→ Phase 7: CleanupPhases 1, 3, 5, and 7 have explicit approval gates. Phase 2 is an interactive Q&A. Phase 5 is a freeform iteration loop that can run many rounds.
When the user invokes the skill with phrasing like "use sane defaults", "don't ask questions", "non-interactive", "just ship it", or any equivalent — interpret it precisely:
It DOES override (skip the prompt, pick the default):
marketing/<feature-slug>/hyperframes/It does NOT override (must always run regardless):
npx hyperframes preview must start and the studio URL must be opened in the browser before the render runs. The user can interrupt; the agent must not pre-decide for them.If you're tempted to skip a HARD-GATE because the user "said no questions" — re-read this section. The user said no questions, not no gates.
Resolve the argument (if provided) in this order:
.md containing "Executive Summary" or "Key Messages") → marketing brief.md with blog post structure) → blog post#\d+ pattern → PR<ref>..<ref> or <ref>...<ref> (alphanumeric + /, _, ., - on each side) → git ref rangeIf no argument is provided, ask: "What should the video be about? You can provide a PR URL/number, marketing brief, blog post, changelog, git ref range, file/directory path, or just describe the feature."
When invoked from the pipeline with a PR and upstream marketing-brief/blog-post paths, read both: PR for technical accuracy, upstream content for positioning/tone.
(Detailed phase specs begin below — see Phase 1.)
Attempt to invoke the hyperframes and hyperframes-cli skills via the Skill tool. If either is unavailable, present:
"Thehyperframes/hyperframes-cliskills aren't installed. Options: a) proceed with baseline knowledge (quality may be reduced) b) wait while you install them c) cancel"
If the user picks (a), emit a warning in the final summary noting reduced quality.
| Input type | What to read |
|---|---|
| Marketing brief | positioning, key messages, audience |
| Blog post | headline, narrative, examples |
| Changelog | highest-impact entry |
| PR | gh pr view <n> --json title,body,files,labels, diff (gh pr diff), commit messages, linked issues |
| Git refs | git diff <range> + git log <range> --oneline |
| Codebase path | read the specified files/directories |
| Freeform | parse the user's description |
For PRs with 20+ files, filter to user-facing changes only — skip tests/, ci/, .github/, lockfile changes, dep bumps.
Error handling:
gh not available → tell the user, ask for an alternative (diff file, freeform description)When the input is a PR, the brief table in Step 1.2 is not enough. PR bodies are routinely vague, outdated, or focused on implementation rather than user value, and a video built off the body alone tends to overclaim or miss the headline angle entirely. Before continuing to Step 1.3, run this structured analysis and produce a written PR analysis block that becomes the source of truth for Steps 1.4 (scope confirmation), Q2.1 (duration ladder), and Phase 3 (narrative planning).
Mandatory steps — do all of them:
gh pr view <n> --json number,title,body,baseRefName,headRefName,files,labels,commits,additions,deletionsRecord baseRefName (usually main / master / develop) — that is the diff reference. gh pr diff <n> automatically compares the PR head against this base.
gh pr diff <n>For very large PRs (>500 lines or >20 files), also list changed files via gh pr view <n> --json files.
tests/, __tests__/, *.test.*, *.spec.*, __mocks__/, ci/, .github/, lockfiles, dep bumps without behavior change, generated/build artifacts (dist/, build/, .next/), and formatting-only diffs. What remains is the user-facing surface of the PR.exports (functions, types, components, hooks, classes, constants)Grep the diff for added lines beginning with export , new top-level function/class/const in src/ / lib/ / packages/*/src/, new files in those trees, and changes to public type signatures. If you cannot point at a line in the diff for a claimed API, the claim is wrong — drop it.
The "had to" half must be a real prior workflow you can describe — copy-pasting an adapter from the docs, installing a second package, writing boilerplate, hitting an error, switching to a different tool. If the honest "Before" sentence is "they could already do this", the PR has no user-visible value delta — see step 8.
"This PR looks like an internal refactor / test-only / dep-bump PR — I can't find a user-visible value delta. Options: a) shift to a performance / DX / cleanup angle if numbers support it b) pick a different PR or input c) cancel"
Do not invent a feature angle to fill the gap. A confabulated angle wastes a full iteration round and destroys user trust on the very first draft.
PR analysis — #1234 "Add fromZodSchema support"
Base: main · Head: feature/zod-schema · Author: <login>
User-facing files: 3 (packages/core/src/index.ts, packages/core/src/zod.ts, packages/core/src/types.ts)
Filtered out: 5 test files, 2 doc files, lockfile
Public-API delta:
+ export function fromZodSchema(schema: ZodSchema): StandardSchema
+ export type ZodCompatibleSchema
~ default error code for ZodError changed: 'invalid_type' → 'STANDARD/type'
Behavior delta:
- Users importing zod schemas no longer need a manual adapter.
- Error messages from zod paths now use the StandardError shape.
Before / after:
Before: copy a 15-line adapter from the docs into every project that mixes zod with this library.
After: one import + one call.
Surprises (in diff but not in PR body):
- Default error shape change (above) — could be the headline angle for migration-aware audiences.
Headline angle: "drop the 15-line adapter — one import, one call"
Scope band for Q2.1: one idea (15–25s)The "Headline angle" line feeds Phase 3.0's motif derivation and Phase 3.3's hook copy. The "Scope band" line feeds Q2.1's scope-derived duration proposal.
For git-ref-range and codebase-path inputs, apply the same structure with the diff coming from git diff <range> / direct file reads in place of gh pr diff. Steps 4–9 (public-API delta → analysis block) are not PR-specific.
Read if they exist: README.md, docs/, package.json. If nothing found, ask: "Can you briefly describe the product and who it's for?"
For PR inputs, the bullets and the compelling angle below must be derived from the PR analysis block written in Step 1.2a — not from the PR title or body. Quote the "Headline angle" line directly as the proposed story seed; turn the public-API delta and the before/after statement into the bullets. If the user added clarifying context after Step 1.2a, fold it in here.
For non-PR inputs, derive the bullets from the relevant entry in Step 1.2 (marketing brief positioning, blog headline, changelog highlight, codebase reading, freeform description).
Present:
"Here's what I'll base the video on: - [feature summary bullet 1 — concrete, grounded in the diff / source] - [feature summary bullet 2 — the before → after value, in one line]
>
The compelling angle: [proposed story seed — for PRs, the "Headline angle" from the analysis block]
>
Anything to add, remove, or correct?"
Do not proceed until the user confirms.
Before continuing, scan for: security patches, internal pricing, credentials, unreleased roadmap items, content marked confidential. Flag anything questionable to the user.
Ask these questions one at a time, in order.
Do not ask the user to pick a fixed length from a menu. (This restriction is specific to duration; other questions like aspect ratio use a numbered menu.) A fixed number becomes a constraint, and the dominant failure mode is padding — freeze-frames, repeated beats, black or empty trailing frames, or filler bullets added solely to reach the chosen target. Instead, derive a duration from the scope of the change and present it as a proposal the user can confirm or override.
Allowed range: 15–60 seconds. Anything outside this window is wrong by default. Going under 15s means the story can't breathe; going over 60s means it's two videos.
Scope → duration ladder:
| Scope of the PR / feature | Distinct payoff beats | Target |
|---|---|---|
| One idea (single API addition, single bug fix, one QoL win) | hook + 1 delivery + CTA | 15–25s |
| Typical PR-sized feature (problem → solution → proof, or a multi-chapter code walk) | hook + 2–3 delivery + CTA | 25–40s |
| Multi-faceted release (multiple distinct sub-features, or comparison needing problem + solution + proof beats) | hook + 3–4 delivery + CTA | 40–60s |
A distinct payoff beat is one new thing the viewer learns. Two scenes whose payoff sentences (see Phase 3.3 item 1) reduce to the same idea are one beat, not two.
Sanity check before proposing — do this silently first:
Propose to the user (no menu, no fixed lengths):
"Based on the scope of this PR, I'm targeting ~Xs (range Ys–Zs). Beats: <one short sentence listing the beats>. Confirm, or override with a different length anywhere in 15–60s."
Hard rule — story sets duration, never the other way around. If at any later phase a scene needs to be stretched, held on a static frame, repeated, backed by black/empty frames, or filled with filler bullets to reach the chosen target — stop, shorten the target, re-confirm with the user, and ship the shorter video. Padding to hit a number is the single behavior this rule exists to forbid. If a 30s scope honestly tells in 18s, ship 18s.
Breathing-room constraint. Whatever duration is chosen, it must allow every on-screen text element to (a) finish animating in, (b) dwell long enough to be read, and (c) settle for at least ~0.4s before the next scene begins. Cutting to a new scene the instant a line of text finishes appearing is forbidden. See Phase 3.3 (item 3) and the Phase 6 audit for the concrete dwell-time table.
"Which aspect ratio? 1. 16:9 landscape (1920×1080, default) — desktop X/LinkedIn 2. 1:1 square (1080×1080) — mobile-friendly feed 3. 9:16 vertical (1080×1920) — Reels/Shorts/TikTok 4. Multi-format — render all three from the same story"
Frame rate is fixed at 30fps.
<HARD-GATE> 1:1 square (and any non-landscape aspect) MUST fill the whole canvas. This is the single most common square-format failure: content clustered in the top ~70% with a dead band at the bottom (or sides). A square is not a cropped landscape — when one axis shrinks, each element earns more space, so scale up, don't shrink.
Concrete rules for every 1:1 scene (and apply the same spirit to 9:16):
justify-content: center (or flex-start with a fixed gap) clusters the blocks in the middle and leaves a big dead band at top and bottom — the classic top-cluster failure.justify-content: space-between does the opposite: it flushes the first and last block hard against the edges (zero edge breathing room) and dumps all the free space into one giant middle gap. If content is 10% of the height with 10% edge margins, the middle gap becomes ~40% — visibly wrong even though the edges "pass."justify-content: space-evenly with gap: 0 is the fix: the n+1 spaces (top, each inter-block, bottom) are all equal. The eye reads even rhythm as "designed"; a 10/40/10 distribution reads as "broken."gap adds a floor that makes inter-block spacing exceed edge spacing, reintroducing the imbalance. Set gap: 0 (or a tiny value) and let space-evenly own all vertical spacing. Padding sets the outer frame inset; space-evenly distributes inside it.npx hyperframes inspect AND eyeball the hero frame of every scene: squint — does the content own the frame with even rhythm, or float in a sea of black / cluster with one fat gap? If the spacing is uneven, switch the scene to justify-content: space-evenly; gap: 0 and enlarge elements until the dead margin is ≤10–20% and evenly distributed.This is enforced at scene-plan time (Phase 3.3) and as a pre-render audit (Phase 6.1) — it is not optional for square output. </HARD-GATE>
"Where should the HyperFrames project live? Default: marketing/<feature-slug>/hyperframes/ (fresh per-video) Override: specify a path."<HARD-GATE> *The brand scan is mandatory. It is not skippable under any user instruction — including "use sane defaults", "don't ask questions", "just ship it", or "non-interactive". Those instructions affect interactive confirmation; they do NOT affect detection.*
You MUST run the heuristics in brand-detection.md against the actual target repository — the source code that owns the feature, not the marketing/ output directory — before selecting any color, font, or logo.
Forbidden shortcuts (these produce wrong colors and waste an iteration):
The scan must produce a written record before any composition file is written. Output a short block listing, for each field: the source file checked, the value found (or not found), and the final value used. Example:
Brand scan — TanStack/ai
Primary : checked tailwind.config.* (none) · packages/*/styles.css (--brand: #0a3d2e) → #0a3d2e
Accent : checked theme.json (none) · brand.json (none) · derived from primary → #14b870
Logo : checked public/logo.svg → media/header_ai.png
Font : checked next/font (none) · @fontsource (none) · README ref → Inter (fallback)If the scan finds nothing for a field, fall through to the neutral defaults below — but only after the scan ran and is recorded. Skipping the scan and going straight to defaults is the failure mode this gate exists to prevent.
In interactive mode, present findings (the block above) and ask for confirmation. In non-interactive / "use sane defaults" mode, print the same block and proceed without asking — the record is required either way. </HARD-GATE>
Present findings as:
"I found: - Logo:media/header_ai.png(copied frompublic/logo.svg) - Primary:#0066ff(fromtailwind.config.js) - Font:Inter(fromnext/font)
>
Use these, customize some, or provide your own?"
Persistence: write chosen brand to .marketing/brand.json (relative to repo root) and to a generated DESIGN.md inside the project (the file the hyperframes skill's Visual Identity Gate requires). On subsequent runs, ask:
"I loaded brand settings from .marketing/brand.json. Use saved, or re-detect?"Fallback when nothing detected: ask explicitly with these neutral defaults. These are intentionally neutral — auto-detection of the project's actual brand is always preferred, and these values should only appear when detection turns up nothing.
#3B82F6 (neutral blue)#8B5CF6 (neutral violet)#0A0A0A (near-black, dark mode)#FFFFFF (white)#9CA3AF#22C55E#EF4444Geist (loaded via Google Fonts <link> in index.html)Confirm with the user before scaffolding.
Note on fonts: The default scaffold loads Geist via a <link> to https://fonts.googleapis.com/css2?family=Geist:wght@400;600;700;900&display=swap. To use a different Google Font, edit DESIGN.md, update .marketing/brand.json, and the skill regenerates index.html and styles.css accordingly. Non-Google fonts require manual self-hosting.
Before picking a story pattern, identify the signature visual motif that will carry the narrative across scenes. This is the single most important call for not looking generic.
references/visual-motifs.md — the catalog maps 20+ common verbs to motifs, state axes, and example custom elements. If the verb isn't listed, apply the heuristic in that file.Confirm with user:
"The core verb is compose music, so I'll use an animated waveform as the signature motif — smooth/clean in the hook and solution, jagged/red in the problem. This thread will appear in scenes 1, 2, and 4. Approve, pick a different motif, or let me propose alternatives?"
Do NOT skip this step. A video without a derived motif defaults to generic bullet-list storytelling and fails the generic test (Storytelling Rule 10).
Scan the PR/input for signals and pick one of 5 patterns. See the detection signals table in patterns/README.md — that file is the single source of truth.
Load the matching pattern spec from patterns/<pattern>.md.
Confirm with user:
"This looks like an [API/Library feature] PR. I'll use that story template. Override? Options: api-library / ui / performance / bugfix / generic / describe a custom pattern"
Per pattern:
Offer user override:
"For code snippets, I'll synthesize realistic usage examples rather than paste raw diff. Override: use-diff / synthesize / mix"
Before synthesizing usage code for a PR, verify the library's actual public API. Do not invent method names, argument shapes, or import paths.
For each snippet the skill plans to include:
packages/<name>/src/index.ts, docs examples in docs/, test fixtures in tests/).If the library isn't available locally and the skill can't verify, ask the user before synthesizing. A wrong API in the first draft destroys user trust and wastes a full iteration round.
This step has two parts: a silent self-improvement loop that you run before the user sees anything, and the user-facing approval gate that follows.
Do not show the user the first thing you wrote. The first version of a scene plan is almost always weaker than the second. The agent's job here is to hand the user the strongest plan it can build given the rules in this skill, not the first draft.
#### Self-improvement loop (silent — run before presenting)
After drafting an initial scene plan, run a deliberate improvement pass against the rule sections listed below. Iterate at least twice. Stop only when one full pass produces zero further changes — the draft has stabilized.
On every pass, for each scene and for the plan as a whole, ask: "Does this satisfy this rule? If not, can I rewrite, merge, drop, split, or reorder a scene to fix it?" Apply the fix in-place, then continue the pass.
The five Core checks below must be satisfied — every plan, no exceptions. On top of those, scan against the broader rule sections at the end of this step.
Core checks (every plan must satisfy all five):
Reject plans where the shortest and longest scene differ by less than ~2×. Equal-slice plans are the single strongest "AI-generated" tell. Reject plans where the CTA is under 4s — a rushed CTA destroys the conversion the rest of the video bought.
| Element | Minimum dwell |
|---|---|
| Short headline / one phrase (≤6 words) | ≥ 1.5s |
| Long headline / single sentence (7–14 words) | ≥ 2.5s |
| Two-line text / short paragraph (15–30 words) | ≥ 3.5s |
| Code chapter (per chapter, after focus lands) | ≥ 3s |
| CTA URL / handle (must be clearly readable) | ≥ 3s |
Every scene must also include a ~0.4s settle hold between the last animation completing and the transition starting. Cutting on the same frame an animation finishes is forbidden — the eye needs a beat to confirm what it saw, and transitions that arrive on the resolve-frame feel cluttered and amateur. If the proposed scene durations cannot accommodate these minima, shorten the beat list, do not shrink the dwell times.
Broader rule sections to scan on every improvement pass. Search this file (or the linked file) for each section heading and walk it against the current draft:
hooks/hook-rules.md) — applied to the HookTitle scene. If it fails any check, rewrite the headline; don't just record the failure./*…*/, pre-break long imports.patterns/<pattern>.md) — the scene sequence and payoffs should be coherent with the template; deviations must have a clear justification.Improvements you may apply silently during the loop (no user question needed):
Changes that must wait for the user — flag them, do not apply silently:
Stop condition. End the loop when one full pass over all rule sections (Core checks + broader sections) produces zero changes. If you hit five passes without stabilizing, you have a structural problem the loop can't fix — stop and ask the user for guidance instead of churning.
#### Present the (already-improved) scene plan for approval
When presenting to the user, include a brief "Self-review notes" line near the top of the response so the user can see the improvement work was actually done. List the 2–5 most material changes the loop applied. Example:
Self-review notes: tightened the hook line from "Add validation easily" to "Swap validation libs with one line"; merged a redundant "why it matters" scene into the problem setup; trimmed delivery 14s → 11s so the CTA dwells ≥4s.
If the loop produced no material changes (rare — usually means the first draft was already strong, or the agent isn't pushing hard enough), say so explicitly: "Self-review notes: draft was stable on first pass; no rewrites needed."
Example output:
"Here's the plan (30s target):
>
1. HookTitle (0–3s, 90f) —"Swap validation libs with one line"— payoff: there's one line that replaces N SDKs 2. ProblemSetup (3–8s, 150f) — three evidence cards with concrete conflicting API shapes — payoff: viewer sees the real API-shape conflict they live with today 3. LibrarySwap (8–22s, 420f) — shared Standard Schema code, import line cycles zod → valibot → arktype — payoff: viewer sees the "one line change" literally happen on screen 4. CTAEndScreen (22–30s, 240f) —"Ship it"+ link to standardschema.dev — payoff: viewer knows exactly where to go next
>
Pacing: hook 3s / problem 5s / delivery 14s / CTA 8s (ratio ~4.7×) — passes variance check. Motif: schema-interop glyph (interlocking rings) appears in scenes 1, 2, 4; rings are disconnected in scene 2, unified in 1 and 4.
>
Approve or adjust any section?"
Do not scaffold until the user approves the scene plan.
See patterns/README.md for how patterns map to scene plans.
Hard cuts between scenes are acceptable; match-cuts are what make a video feel crafted. When the signature motif (Phase 3.0) appears in adjacent scenes, the motif must carry over as a match-cut, not restart from zero.
Rules:
data-bg variant must change between adjacent scenes (no two adjacent scenes share a variant). This is enforced at pre-render.Write the planned transitions into the scene plan before scaffolding:
Transition 1→2: waveform carries over at the same position; color fades from brand.primary → brand.danger; amplitude jitters from smooth to glitchy over 8 frames. Transition 2→3: glitch waveform contracts into a flat line → that line becomes the top border of the code card in scene 3. Transition 3→4: active-provider pill in scene 3 slides down and morphs into the URL pill of the CTA.
If the feature has a visible UI surface — a generated image, a rendered audio player, a dashboard, a settings toggle, a diff view — the video must include at least one beat that shows that surface. Research on product-launch video is consistent: "show the product in action" is the strongest single predictor of viewer recall.
Options (in order of preference):
<video src=… autoplay muted playsinline loop> inside a UIShowcase scene with data-media-kind="video".Place the UI moment in the delivery scene (Scene 3 in the default structure), ideally at its midpoint so the viewer gets code-plus-result. A code-only delivery feels like a reference doc; code-plus-UI feels like a demo.
Skip this step only if the feature is purely API-level with no user-visible surface (a parser, a compiler pass, a type-level utility).
When the user picked option 4 (Multi-format) in Q2.2, the skill must plan per-aspect narrative variants — a 9:16 vertical video is not just a cropped 16:9.
#### Rule 0 (the load-bearing one): Fill the canvas. Don't just shrink content.
The most common failure mode when porting a 16:9 layout to 1:1 or 9:16: the agent keeps the original element sizes, only changes the canvas dimensions, and ships a video where content occupies ~50% of the new canvas with huge dead margins on top/bottom (vertical) or sides (square).
The right move is the opposite of intuition: when the canvas gets smaller in one axis, the content's per-element size needs to get bigger, not smaller. Less competing content = each element earns more visual space.
Concrete defaults when porting from 1920×1080 (landscape) to other aspects:
.hook-stack gap of 56px on landscape becomes ~80px on 9:16. Whitespace is doing brand work — keep it generous.Run the fill check mentally before rendering and as a hard pre-render audit:
At the hero frame (peak content) of every scene at every aspect, the bounding box of the foreground content reaches within ~80–100px of every canvas edge. If any scene at any aspect has more than ~120px of dead margin on either axis, the override CSS is wrong — the content is undersized.
>
AND: the vertical spacing must be even. Measure the gap above the first block, between each adjacent pair of blocks, and below the last block — they should be roughly equal (within ~1.5×). A scene can pass the edge-margin check above and still fail here if it usesjustify-content: center(clusters in the middle, fat top+bottom bands) orspace-between(flushes outer blocks to the edges, one fat middle band). The fix isjustify-content: space-evenlywithgap: 0so all n+1 spaces are equal. See the Q2.2 HARD-GATE.
If you're tempted to keep all the landscape sizes "since they look fine on landscape" — stop. They look fine on landscape because they fill landscape. They will not fill a different aspect at the same sizes.
#### Layout-shape adjustments
BeforeAfter, multi-pill pill rows, and wide LibrarySwap layouts must switch to stacked (top/bottom) or single-item-at-a-time layouts on 9:16. The bundled scenes use [data-aspect="vertical"] selectors to fall back to single-column grids; verify each scene's CSS includes that fallback before rendering vertical.vw/% units, not hardcoded pixels. Review any reusable motif fragment for responsive sizing before scaffolding.tanstack.com/ai instead of https://tanstack.com/ai/audio-generation) for 9:16.Write the per-aspect deltas into the scene plan as a short section, even if most scenes render identically. Example:
9:16 deltas: Scene 3 LibrarySwap stacks the code card above the provider pills (same content, vertical flow). Headline 132px → 144px (kept large). Padding 32px → 200px top/bottom. Code font 18px → 18px (held). Total duration shaved to 26s (hook 2.5s / problem 4s / delivery 12s / CTA 7.5s).Skip this step if the user picked a single aspect.
(Caption emphasis syntax, text alignment, background variants, and code-block theming are governed by the Layout and Typography Rules section below.)
At the location chosen in Q2.3 (default marketing/<feature-slug>/hyperframes/):
npx hyperframes init <project-path> --non-interactive --example blankIf --example blank is not available in the installed CLI version, run npx hyperframes init --help to see the current example list and pick the most neutral one (an empty / minimal template). Document the substitution in the final summary.
This creates the HyperFrames skeleton (package.json, base index.html, compositions/, registry config, FFmpeg + Chrome integration). The skill then overlays these files from skills/hyperframes-video/templates/:
<project-root>/
├── DESIGN.md # written from templates/project/DESIGN.md.template + Q2.4 brand
├── styles.css # written from templates/project/styles.css.template + brand tokens
├── index.html # replaces init-generated; from templates/project/index.html.template
├── .hyperframes/
│ └── shiki-theme.json # from templates/project/shiki-theme.json.template + brand colors
├── .marketing/
│ └── brand.json # machine-readable brand record (also copied to repo root)
└── compositions/
└── scenes/ # all 9 scene HTMLs from templates/scenes/Verify the init-generated index.html clip-include syntax and mirror it in the overlay. If the CLI version uses a different include shape (e.g. <iframe>, shadow includes, or a JS manifest), the overlay must follow that shape — read the init output before substituting {{sceneIncludes}}.
npx hyperframes init handles its own install. The skill additionally installs shiki as a dev dependency. shiki runs at scaffold time AND during iteration when code or brand changes — the highlighted-HTML output is regenerated by Phase 5.2 / Step 4.3c, not hand-edited. shiki is NOT shipped to the rendered video output (the HTML it produces is baked in):
Detect package manager from lockfile (pnpm-lock.yaml → pnpm, bun.lockb → bun, yarn.lock → yarn, package-lock.json → npm). Default repository preference: use the lockfile's package manager. Example (pnpm):
pnpm --dir marketing/<feature-slug>/hyperframes install --save-dev shikiFor every scene with code (CodeSnippet, BeforeAfter, LibrarySwap):
.marketing/brand.json into <project>/.hyperframes/shiki-theme.json (substituted from templates/project/shiki-theme.json.template).<span data-line="N">…</span> so the chapters mechanism can target lines.<pre class="hf-code">…</pre> block.Brand-derived theme: keywords in var(--brand-primary), strings in var(--brand-accent), comments in var(--brand-muted), transparent background. Do NOT fall back to vitesse-dark, github-dark, or any other stock theme unless the brand is explicitly monochrome.
When the user picked option 4 (Multi-format) in Q2.2, the skill writes three composition files instead of one:
index-landscape.html — data-composition-id="MainLandscape", 1920×1080, data-aspect="landscape"index-square.html — data-composition-id="MainSquare", 1080×1080, data-aspect="square"index-vertical.html — data-composition-id="MainVertical", 1080×1920, data-aspect="vertical"All three reference the same scene snippets in compositions/scenes/; per-aspect layout adjustments come from [data-aspect="..."] selectors in styles.css and from any aspectOverrides hints in the approved scene plan.
When the story plan calls for a scene shape the 9 bundled templates can't express, generate a custom scene under compositions/scenes/custom/<Name>.html.
Rules (enforced by the skill):
<style> + GSAP <script>.styles.css brand tokens, the global GSAP include, and shared data-bg variants. No external CSS frameworks, no other animation libs.npx hyperframes lint and npx hyperframes inspect --json before render — skill self-corrects up to 2 times on failure.[data-clip-id="..."] (see the gsap skill). No setTimeout-based animation.data-duration from its entry in the parent composition.index.html's clip list alongside the bundled scenes.Use the hyperframes skill's authoring guidance (Layout Before Animation, palette discipline, motion principles) when writing the custom scene.
<HARD-GATE> The preview is mandatory and must be visibly running in the user's browser before any render. This gate is non-negotiable.
It is not skipped by "use sane defaults", "don't ask questions", "non-interactive", "just ship it", "just render it", or any equivalent instruction. Those phrases govern configuration questions (Q2.1–Q2.4 confirmations) and iteration prompting — they do NOT authorize skipping straight to render. The user is always given the chance to see the first draft moving on screen and steer it before render burns time.
Forbidden shortcuts:
npx hyperframes render" → No. Render is gated by Phase 6, which is gated by Phase 5.Required actions in this phase, in order:
npx hyperframes preview --port 3010 as a background process (fall through 3011/3012/3013 if busy).Studio running in the process output before proceeding.open on macOS, xdg-open on Linux, start on Windows). Do this once, automatically — do not just print the URL and assume the user will click it.If the user has explicitly disabled the preview gate in advance (e.g., a CLAUDE.md note, a one-off "render only, no preview"), record that override in the final summary. Vague "use defaults" phrasing is NOT such an override. </HARD-GATE>
Run as a background process, scoped to the scaffolded project directory:
npx hyperframes preview --port 3010(Fall back: try 3011, 3012, 3013 if 3010 is in use. Fail loud if all taken.)
Wait for Studio running in the preview process output, then open the URL in the user's browser:
# macOS
open http://localhost:3010
# Linux
xdg-open http://localhost:3010
# Windows
start http://localhost:3010Then present to the user:
"Preview is running and I've opened http://localhost:3010 in your browser. Review the first draft.
>
What do you want to change? (freeform — 'make the hook punchier', 'swap scenes 2 and 3', 'use arktype instead of yup', 'drop the problem scene', 'longer pause on the code', anything you want.)"
Parse the user's request and decide which file to edit:
| Request kind | File to edit |
|---|---|
| Content / copy change | scene HTML's text/caption/code (re-run shiki for code changes) |
| Timing / ordering change | parent index.html clip order + data-duration |
| Visual / animation tweak on a bundled scene | compositions/scenes/<Scene>.html |
| Novel visual request the bundled set can't express | compositions/scenes/custom/<Name>.html (create) + reference in index.html |
| Brand change (colors, font, logo) | DESIGN.md + .marketing/brand.json + regenerate styles.css + regenerate shiki theme |
After each edit:
npx hyperframes lint silently (current directory).npx hyperframes inspect --json silently.Re-prompt:
"Saved. Preview should have refreshed. Next change, or ready to render?"
User signals "ready to render", "looks good", "ship it", "render", "done", or similar → move to Phase 6.
Each iteration stores a snapshot of the parent index.html content + scene contents in an in-memory session history. If the iteration loop hits 10 rounds without approval:
"We've iterated 10 times. Options: 1. Reset to an earlier draft (I'll show the history) 2. Keep going 3. Render the current draft as-is"
If the user picks (1), show a numbered list of snapshot summaries (hook text of each draft) and let them pick.
Once a video has been rendered at least once, do NOT re-render automatically after each subsequent edit. Make the requested change, run `lint` + `inspect`, and wait for the user to explicitly ask for a render before producing a new mp4.
Why: renders are slow (1–2 min for a final pass) and the live preview already HMRs every edit, so the user can see changes instantly without a render. Re-rendering after every small tweak burns time the user didn't ask to spend.
#### Render quality is not optional
The preview is pristine; the render pipeline must preserve that. Two structural problems ruin h264 output of otherwise-perfect HTML scenes:
on dark backgrounds, thin colored lines, and subpixel anti-aliasing get blurred. The fix is supersampling: render at 2x composition dimensions so ffmpeg downsamples from a sharper source.
The fix must be at the source, not at the codec: every scene's [data-bg] block ships a subtle SVG noise overlay (the .scene::after feTurbulence layer in styles.css) that dithers the quantization below perception. 8-bit h264 encoding is the only format that plays reliably across Windows 11 Films & TV, Discord, browsers, VLC, and social upload pipelines. Do NOT default to 10-bit (yuv420p10le) — consumer GPU decoders often skip h264 high10 without falling back to software, producing black frames on Win11/Discord.
Edge case — 10-bit opt-in: only use when the creator has verified their end-to-end pipeline supports h264 high10 profile (e.g., shipping to a known YouTube upload that always transcodes, or to a target audience on mpv/VLC only). Never use as a default.
Never render with low-quality JPEG intermediates. Use PNG or HyperFrames' default lossless intermediate.
How the NoiseDither overlay works: every .scene element has an ::after pseudo-element that paints an SVG feTurbulence fractalNoise filter at ~4% opacity in overlay blend mode. The noise breaks up the 8-bit gradient-quantization step boundaries perceptually, turning visible bands into faint grain. This is the same technique Figma and export pipelines use.
Before rendering, all of these must pass. If any fail, report exactly what and where, and do not render.
Technical checks:
npx hyperframes lint passes with zero errorsnpx hyperframes inspect --json finds no overflow / off-canvas / clipped-container issues<clip>.dataset.duration across the composition is within the 15–60s window AND within ±20% of the scope-derived target proposed in Q2.1 (or matches an explicit user override). If the natural story comes in shorter than the proposed target, the target was wrong — re-derive from scope, do not pad.localhost:30XX, watch the longest text-heavy scene, and confirm each substantial caption stays on screen ≥1.5s after its animate-in completes. If any caption flashes faster than that, slow the animate-in or add a hold before the scene-out.DESIGN.md exists on diskStorytelling audit (refuse to render if any fail — report to user and ask for a fix before proceeding):
hooks/hook-rules.md) pass on the HookTitle scene's headline textcompositions/scenes/)max(scene.duration) / min(scene.duration) ≥ 2.0. Uniform-duration plans refuse to render.0.30 × total duration. If more than 30% of the video elapses before scene 3 begins, the value prop is buried.data-bg variants (prevents the "same color for 15 seconds" flatness)z-index declaration. npx hyperframes inspect covers most of this; verify any newly-added decorations against the rendered hero frame, not the empty initial frame. See Layout Rule 7.chapters[] (or equivalent timeline-driven [data-active] progressions). Static one-shot code dumps fail this check. See Code Scene Rules — Tell a story with chapters.chapters[] (or staged code focus) MUST have a heading/caption variant per chapter that swaps in lockstep with the focus change. A code highlight without a synchronized title saying why we're looking at it fails this check. See Code Scene Rules — Synchronized chapter narration.justify-content: center clustering content in the middle with fat top+bottom bands, or space-between flushing outer blocks to the edges with one fat middle band — even when the edge margins "pass." Fix (a) by enlarging elements; fix (b) by switching the scene to justify-content: space-evenly with gap: 0. See the Q2.2 1:1-fill HARD-GATE and Step 3.6 — Fill the canvas.Stop the preview background process first.
For a single-aspect video:
npx hyperframes render --output out/video.mp4For multi-format (user picked 4 in Q2.2), render each composition by id:
npx hyperframes render --composition MainLandscape --output out/video-landscape.mp4
npx hyperframes render --composit~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.