name: launch-film description: | Direct and finish a product launch film — a 30–60s cinematic hero video for a Product Hunt / X / landing-page launch. Authors a HyperFrames HTML composition, paces every scene so nothing sits dead, renders to 1080p, then applies a one-pass cinematic finish (warm grade + fine film grain + vignette) sized for upload. The exact pipeline behind the Franklin Agent launch film. triggers: - "launch video" - "launch film" - "product hunt video" - "hero video" - "promo video" - "make a launch trailer" - "cinematic finish" - "film grain" - "grade this video" - "色调电影感" - "产品发布视频" user-invocable: true allowed-tools: Bash(npm:), Bash(npx:), Bash(hyperframes:), Bash(ffmpeg:), Bash(ffprobe:), Bash(mkdir:), Bash(ls:), Bash(cp:), Bash(open:), Bash(chmod:), Read, Write, Edit
/launch-film — direct + finish a cinematic product launch film
Takes a product brief and produces an upload-ready launch film with the same
craft as a professionally graded reference (Plasma / Reflect-tier): an
editorial, documentary look; continuous motion in every scene (no dead
holds); and a filmic finish pass that lifts a clean web render into something
that reads like film. Validated on the Franklin Agent Product Hunt launch
(franklin-ph-v20.mp4, 1080p/30, 54.6s, 28 MB).
This skill is authoring + finishing, not a single API call. It composes in
HyperFrames HTML (deterministic, seek-accurate) and finishes with ffmpeg. The
finish pass (finish.sh) is standalone — point it at any render, including
a Seedance clip from /dance, to give it the same grade.
What "professional" actually is (the two gaps it closes)
Almost every self-made launch video fails the same two ways. This skill targets exactly those:
- Dead scenes. A card holds still for 6–8 seconds while the VO talks. The eye disengages. Fix: every scene carries a slow, continuous camera move for its entire duration, plus a beat in any lull.
- Flat finish. A clean HTML/screen render looks like a render. Reference launch films are graded and grained. Fix: a single warm grade + fine grain + vignette pass — the single biggest perceived-quality jump available.
Inputs
| Input | Required | Default | Notes |
|---|---|---|---|
| brief | yes | — | What you're launching + the beats (hook → problem → reveal → demo → proof → pricing → CTA). |
aspect |
no | 16:9 |
16:9 for Product Hunt / landing; 9:16 for Shorts/Reels/TikTok. |
duration |
no | ~50s |
30–60s is the launch-film sweet spot. |
intensity |
no | normal |
Finish strength: subtle, normal, strong. |
look |
no | warm-doc |
Named grade: warm-doc (parchment documentary), cool-tech, noir. |
target_mb |
no | 50 |
Upload ceiling; the finish auto-steps quality down once to fit. |
Prerequisites (self-contained — no other skills required)
This skill bundles everything it needs. Two external tools:
- HyperFrames — invoked on demand via
npx --yes hyperframes@<version> <cmd>(no global install). The bundledstarter/package.jsonpins a known-good version. Authoring reference any time:npx hyperframes docs data-attributes/gsap/compositions. - ffmpeg — for the finish pass (
brew install ffmpeg).
You do not need separate hyperframes/gsap skills installed — the
starter/ composition below is a complete, lint-clean, render-tested worked
example you copy and edit.
Quick start (the bundled starter renders as-is)
mkdir my-launch && cp skills/launch-film/starter/* my-launch/ && cd my-launch
npm run check # lint + validate + inspect — 0 errors out of the box
npm run render # → renders/<name>.mp4 (1080p/30, ~24s placeholder film)
SRC=$(ls -t renders/*.mp4 | head -1)
../skills/launch-film/finish.sh "$SRC" final.mp4 # cinematic finish
For 9:16 (Shorts / Reels / TikTok), copy starter-vertical/* instead —
same scenes, 1080×1920, also render-tested clean.
Then rewrite the four scenes' copy/visuals for your product, keeping the motion structure. The starter already demonstrates every technique below: push-in, drift, lean-in, ink-underline beat, blur-fade crossfades, editorial look.
Framework gotchas the starter encodes (learned the hard way — keep them):
- The root
data-composition-iddiv MUST be the first child of<body>— a leading comment/element before it breaks root detection (lint failsroot_missing_composition_id). Standalone files use no<template>.- Crossfading scenes overlap in time, so adjacent scenes must use different
data-track-index(alternate 1/2) — same-track clips can't overlap.- Continuous push-in/drift nudges a full-bleed container a few px past its scene box. That's intentional motion — mark those elements
data-layout-allow-overflowsoinspectstays clean.
Workflow (Claude executes in order)
Step 1 — Block the scenes
Turn the brief into 5–8 scenes on one root GSAP timeline. A proven launch arc:
hook (a line that stops the scroll) → problem → reveal (the product name/wordmark) → demo (a terminal / UI / the thing working) → proof (model picker, logos, a number) → pricing → CTA.
Each scene is a class="clip" section with data-start / data-duration /
data-track-index, transitioning via blur-fade helpers:
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true }); // register under your composition-id at the end
const XB = 14, XD = 0.7; // transition blur px, duration
function trIn (sel, t, d){ tl.fromTo(sel,{opacity:0,filter:`blur(${XB}px)`},{opacity:1,filter:"blur(0px)",duration:d||XD,ease:"sine.inOut"},t); }
function trOut(sel, t, d){ tl.to (sel,{opacity:0,filter:`blur(${XB}px)`,duration:d||XD,ease:"sine.inOut"},t); }
// ... build scenes ...
window.__timelines["launch"] = tl; // key MUST equal the root data-composition-id
Step 2 — The pacing rule: NObody holds still
This is the core craft. For every scene, add a continuous move that runs the scene's whole length — not just the in/out transition. Pick per scene:
| Move | When to use | GSAP pattern (real, from the Franklin film) |
|---|---|---|
| Push-in | Hero portrait / product shot | tl.fromTo("#s1-wm",{scale:1.0,opacity:0},{scale:1.14,opacity:.92,duration:8.2,ease:"sine.inOut",transformOrigin:"62% 42%"},0) |
| Drift | Text-heavy / pricing cards | tl.fromTo("#s6 .scene-pad",{y:10},{y:-14,duration:7.4,ease:"sine.inOut"},41.0) |
| Lean-in on a result | Terminal / UI demo (examples/03-terminal-camera.jpg) |
tl.fromTo("#s3-term",{scale:0.96},{scale:1.13,duration:5.4,ease:"power1.inOut",transformOrigin:"50% 64%"},13.9) |
| Slow breathe | Anything still otherwise | tl.fromTo("#card",{scale:1.0},{scale:1.035,duration:5.2,ease:"sine.inOut"},t) |
Then drop a beat into any lull — a moment of motion where the eye would otherwise drift. The Franklin film's hook had a dead ~3–7s zone; a gold ink underline drawing in fixed it and tied to the "wallet/money" theme:
// a beat in the old dead zone
tl.fromTo("#s1-ink",{scaleX:0},{scaleX:1,duration:0.8,ease:"power2.inOut"},3.3);
// typewriter reveal works as a beat too
tl.fromTo("#s3-type",{clipPath:"inset(0 100% 0 0)"},{clipPath:"inset(0 0% 0 0)",duration:1.1,ease:"steps(46)"},14.5);
Diagnostic — run it before rendering: scrub the first 8 seconds. If any element holds still for more than ~1.5s, the scene reads dead. Add a move.
The example frames examples/01-front-pushin-early.jpg (t≈1.8s) and
examples/02-front-pushin-late.jpg (t≈6.4s) show the same hook scene mid-move:
the portrait has scaled up, the ink underline has drawn in, the third line has
revealed — the card is never static.
Step 3 — Validate, then render
npm run check # lint + validate + inspect — fix all errors first
npm run render # → renders/<project>_<timestamp>.mp4 (1080p/30)
Keep the VO/music/timeline intact across iterations — pacing and finish should never desync narration.
Step 4 — Cinematic finish pass (the biggest quality jump)
Run the bundled finish tool on the fresh render. It grades, grains, and encodes in one pass from the source:
SRC=$(ls -t renders/*_*.mp4 | head -1)
LOOK=warm-doc INTENSITY=normal TARGET_MB=50 ./finish.sh "$SRC" renders/<name>-final.mp4
finish.sh env knobs (all optional, all backward-compatible):
| Var | Values | Default | Effect |
|---|---|---|---|
LOOK |
warm-doc,cool-tech,noir |
warm-doc |
color grade character |
INTENSITY |
subtle,normal,strong |
normal |
grade + grain strength |
TARGET_MB |
int | 50 |
upload ceiling (auto step-down) |
PRECISE |
1 |
off | 2-pass to hit TARGET_MB exactly |
NORMALIZE_AUDIO |
1 |
off | loudnorm to −14 LUFS (platform standard) |
GRAIN/VIGNETTE |
0 |
on | disable a layer |
VERIFY |
0 |
on | motion self-check on the result |
What warm-doc/normal applies (filmic grade → fine grain → gentle vignette):
-vf "eq=contrast=1.05:saturation=1.06:gamma=0.98,\
colorbalance=rs=0.02:rm=0.02:bs=-0.02:bm=-0.02,\
noise=alls=8:allf=t+u,\
vignette=PI/5"
-c:v libx264 -preset slow -crf 21 -pix_fmt yuv420p -c:a copy -movflags +faststart
examples/04-cinematic-finish.jpg is a graded frame: warm parchment, fine
grain, subtle corner vignette — still elegant, not muddied.
⚠️ The one mistake that costs you 400 MB. Do not grain a clip and then re-encode the grained file — grain is high-frequency noise that defeats inter-frame compression and balloons the file (the Franklin grade did this: 443 MB). Always grade + grain + encode in one pass from the clean source. Same look, ~28 MB.
finish.shdoes this correctly; the danger is only if you hand-roll it.
Step 5 — Verify motion survived, then ship
OUT=renders/<name>-final.mp4
ffprobe -v error -show_entries format=duration,size -of default=nk=1 "$OUT"
for t in 2 6 16; do ffmpeg -y -ss $t -i "$OUT" -frames:v 1 /tmp/v_$t.png -loglevel error; done
open "$OUT"
Confirm: front frames differ (push-in is moving), size is under target_mb,
audio plays. Report path + size + duration.
Step 6 (optional) — Sound design: the last 10% you hear but can't see
Reference launch films are half heard. sound-design.sh synthesizes the four
workhorse hits with pure ffmpeg (no asset files) and mixes them onto the film at
your cut/beat timestamps — works whether the film already has a music bed or is
silent:
# T:TYPE — whoosh (transition) · tick (reveal) · sub (impact) · riser (into CTA)
./sound-design.sh final.mp4 final-sfx.mp4 6.3:whoosh 12.3:whoosh 12.9:tick 17.8:riser 19.0:sub
Place a whoosh on each scene crossfade, a tick where a word/tag lands, a
sub where a number or logo hits, and a riser ~1.5s before the CTA. SFX_GAIN
(default 0.8) sets the master level. Run this after the picture locks.
Failure handling
| Symptom | Fix |
|---|---|
| Output is 300 MB+ | You re-encoded a grained file. Re-run finish.sh from the clean source render, not the graded one. |
| Front 8s feels dead | A scene is holding still. Add a continuous push-in/drift for the scene's whole duration (Step 2), plus a beat in the lull. |
| Grade looks muddy / crushed | Drop to INTENSITY=subtle, or GRAIN=0 / VIGNETTE=0 to isolate which layer is too strong. |
| Audio dropped after finish | finish.sh uses -c:a copy; if you hand-rolled ffmpeg you omitted it. Copy the audio stream through. |
npm run check errors |
Every timed element needs data-start/data-duration/data-track-index and class="clip"; timelines must be paused and on window.__timelines. See the hyperframes skill. |
| Motion gone after finish | Grain/CRF too aggressive hid subtle moves — lower NOISE, or raise scene move amplitude (scale delta ≥ 0.10). |
Examples
/launch-film "Franklin Agent — agents that can pay. Product Hunt launch, ~50s."
/launch-film "DevTool X launch" --aspect 9:16 --look cool-tech --intensity strong
# Finish-only: grade an existing render or a Seedance clip from /dance
LOOK=noir ./finish.sh ./blockrun-out/<ts>-dance/dance.mp4 dance-graded.mp4
PRECISE=1 TARGET_MB=8 NORMALIZE_AUDIO=1 ./finish.sh raw.mp4 final.mp4
INTENSITY=subtle GRAIN=0 ./finish.sh raw-screen-capture.mp4
The look that ships (style notes from the Franklin film)
- Editorial/documentary, not "SaaS gradient": a parchment field, a serif
display face for headlines, a mono micro-label above each (
PAY-AS-YOU-GO · NO CREDIT CARD), one restrained accent (gold). - One idea per scene. The screen says less than the VO.
- Transitions are blur-fades, ~0.7s — soft, filmic, never hard cuts.
- Sound design is the last 10% the eye can't see but the ear feels —
transition whooshes, a tick on reveals, a sub-thud on a number landing, a
riser into the CTA. Use
sound-design.sh(Step 6) after the picture locks.
Cost
Free — local authoring + ffmpeg. No API calls. (If you generate B-roll for a
scene via /dance or Seedance, that clip is billed by that skill; pipe it
through finish.sh to match the film's grade for free.)