| 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 bundled starter/package.json pins 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
npm run render
SRC=$(ls -t renders/*.mp4 | head -1)
../skills/launch-film/finish.sh "$SRC" final.mp4
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-id div MUST be the first child of <body>
— a leading comment/element before it breaks root detection (lint fails
root_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-overflow so inspect stays 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 });
const XB = 14, XD = 0.7;
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); }
window.__timelines["launch"] = tl;
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:
tl.fromTo("#s1-ink",{scaleX:0},{scaleX:1,duration:0.8,ease:"power2.inOut"},3.3);
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
npm run render
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.sh does 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:
./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
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.)