Agent skill · product-film
Product film: silent, looping, your product as its own components.
A skill for coding agents to make a short silent product film – website hero, landing section, social cut – as a deterministic HTML timeline rendered with Playwright and cut with ffmpeg. The numbers come from sixteen cuts of a shipped SaaS hero film; they work for any product with a UI.
What it builds
Silent, looping films of 20–60 s with full-frame cuts, the product shown as its own real components on a 3D plate with a camera that never stops. One engine, two routes.
With footage
Licensed live-action cuts of people alternate with product beats. Each product beat follows the human action that motivates it: someone at a laptop, then the prompt; a meeting, then the share dialog.
Pure product
Product beats only, joined by the plate, with captions or end cards if the surface needs words. Faster to make and needs no licence.
Keep the rig in one place. Put the camera code and the numbers below in references/camera-rig.md next to the skill and read it before writing any beat. Tune from there; do not re-derive.
When to use
Use it for
- A new hero, section or launch film, or a recut, new beat or new ending of an existing one.
- Showing the product at fidelity, in motion, without screen-recording the real app.
Not for
- Feature walkthroughs captured from the live app with test data.
- Films with music or a voice track – use an editor timeline for those.
- Deploying the finished MP4 to your site.
Inputs to settle before rendering
Ask only for what cannot be inferred; state assumptions otherwise.
| Input | What to settle |
|---|---|
| Reference | A film the user points at. Fetch it, find the video URL, probe it with ffprobe: resolution, fps, length, bitrate, audio track, poster, preload. The spec becomes the target; the structure becomes the grammar. |
| Story and screen | One line: what the user does in the product, who reacts, how the result reaches them. Which screens appear (dashboard, editor, dialog, notification) and their exact copy. |
| Route | A if people footage is wanted and licensable; B if the film is product-only or footage is out of scope. |
| Surface and length | Home hero loop (30 s, silent, poster, preload="none") · landing section (20–25 s) · social (45–60 s, captions; 9:16 reflow is a separate pass). |
| Brand | The product's design tokens, fonts and any third-party brands shown in the UI. |
The engine
Every product beat is an HTML page that exposes window.seek(t) and draws frame t deterministically. A renderer opens it headless at 1920×1080 with device scale factor 2, calls seek(i/25) for each frame and screenshots to JPEG; ffmpeg encodes the beat with grain and a vignette. An assembly script cuts footage segments (Route A) and concatenates everything into a master, a 1080p web file, a 720p review file and a poster.
| Layer | File | Role |
|---|---|---|
| Beats | beats.html | One <div class="beat"> per beat, real product markup and CSS inside, 3D world and camera, particles on a canvas, seek(t) |
| Renderer | render.js | Playwright frames → seg/<beat>.mp4; an ONLY=b4,b6 env var renders a subset |
| Assembly | assemble.sh | Cut list → segments → concat → master → 1080p, 720p, poster |
| Review | review-vNN.html | 720p and poster embedded as base64, "What changed since" on top, republished as new versions of one shared page |
Build these four once as templates in the skill's templates/ folder and copy them for each film; do not start from a blank file.
Product fidelity
The UI on screen must be the product's own components, not a mock-up.
- Markup and CSS from your shipped frontend components or an existing marketing render. Collect them into one product stylesheet inside
beats.htmland reuse the markup with new copy. - Colours from your design tokens. Override only what the film needs, such as an accent.
- Emails and notifications follow the real templates: subject format, headline phrasing, link colour.
- Fonts load at render time; the renderer waits for
document.fonts.readybefore the first frame.
When a component isn't available as code, rebuild it from the shipped UI and your tokens, and say so on the review page.
The numbers
These held across sixteen cuts of a production hero film. Start here; change one at a time and re-render only the beat you changed.
Pacing
- Route A: footage and product each take half the running time. Five cuts per ten seconds, always short–short–long.
- Route B: beats 2.5–5 s, joined by the plate; one idea per beat; captions change on beat boundaries.
- Longest hold on the text-heaviest beat. The first product beat carries the most text.
Camera
A perspective world at perspective:1500px; the camera translates on z – a dolly, never a 2D scale.
| Rule | Value |
|---|---|
| Move | One per beat, cubic ease-in-out over the whole beat: z from about −80 to +180…+500 |
| Drift | x += 6·sin(0.9t), y += 4·sin(1.25t) under the move, so a hold never freezes |
| Rotation | Hand-held objects only (popovers, dialogs, phones): settle from 7° / −9° to 1.5° / −2°, plus ±0.6–1.2° sine. Never on screen-fixed objects (input bar, main card) – tilt there reads as a bug. |
| Subject vs ground | Subject to z 260–280, scale 1.45–1.7×. Ground scales 0.98, dims to 0.86, blurs ≤ 1.6 px as the focus plane follows the subject. The ground is never removed. |
| Depth of field | blur = clamp((|z − focus| − 90) / 110, 0, 3.5) px; opacity × (1 − 0.1·blur/3.5) |
Entrances
- Every element has one: y +44→0, scale 0.94→1, opacity 0→1, 0.3–0.6 s, cubic ease-out. Stagger siblings 60–140 ms in reading order. Chips and avatars pop with a back-ease (1.4→1).
- Text is typed, not faded: ~45 characters per second with a following caret; the camera tracks the caret.
- Unfolds use max-height 0→N with ease-out, not opacity alone.
Plate and texture
- Two soft colour blobs on an off-white plate, 150–200 seeded particles drifting; on the key beat they converge into the main object as it lands.
- Glass panels:
rgba(255,255,255,.7)withbackdrop-filter: blur(22px). Popovers, pills and dialogs are solid white with a border and a deep shadow. - Encode every beat with
noise=alls=6:allf=t+uandvignette=PI/6:mode=backwardso it sits with footage.
Footage (Route A)
- One shoot for every people shot, one protagonist, one mild grade:
eq=saturation=0.92:contrast=1.03. - Each product beat is preceded by the human action that motivates it.
- Wide exterior as opener and closer so the loop seams.
- Licence at the highest tier; pull the largest file. Do not scale-then-refit footage expecting a zoom – a real push-in is
zoompanon an 8K upscale.
Procedure
- Working folder outside the repo and outside any temporary scratch space, e.g.
~/films/<film>/. Agent scratch directories get pruned and take the session's work with them. - Reference and concept. Probe the reference. Write a one-page concept: what the film is not, who the protagonist is, the beat sheet, the register of the humans (Route A). Share it for review.
- Storyboard. Route A: search a stock-footage library through a headed, persistent browser profile (bot protection blocks headless), sample frames, build the storyboard from real frames – all shots from one shoot, every shot 0.5–2 s. Route B: the beat list with the copy per beat. Iterate as versions of one page.
- Sources. Route A: the user buys the licence and downloads the files; they go in
footage/. Route B: nothing to fetch. - Beats. Copy
templates/beats.html, paste the product stylesheet, build one beat at a time; check each with a contact sheet (ffmpeg -vf "fps=2.5,scale=480:-1,tile=4x2") before rendering the next. - Render with
render.jsfrom a directory whererequire('playwright')resolves. - Assemble with
assemble.sh: cut list with in-points, durations and per-cut comments; build; make a whole-film contact sheet at 1 fps and look at it. - Review page. Embed the 720p and the poster, write "What changed since cut vN", publish it as a new version of the same review page. Send the MP4 to the user as a file. Post frames, not prose, when asking for a decision.
- Loop on feedback. Collect open comments, act on each, reply with what changed, close the thread.
- Ship the 1080p file and poster to your site, with the poster at the same frame the film uses.
Review discipline
- Never publish a cut as a new page: one page, many versions, the change log on top of each.
- Before sending any cut, look at four frames of every changed beat at full size – start, mid, end and the frame after the last change. Clipping at the frame edge and blurred children are the two recurring defects.
- Keep a short note on the film current before the session ends: the current cut, the pipeline, what the user rejected and why.
Gotchas already paid for
| Symptom | Fix |
|---|---|
| Children go soft | A CSS filter on a parent blurs its children; sharp objects must be siblings of blurred panels. |
| Slow push jitters | zoompan at 1080p jitters; compute it on a 7680-wide upscale. |
| Concat can't find files | The concat list's paths resolve relative to the list file. |
| Script breaks on vars | zsh mangles $var:flags; write assembly scripts in bash. |
| Phone UI in 16:9 | A floating device on the plate with the camera pushing into its screen. |
| Email after a phone shot | Show the mobile mail app, not the desktop one. |
What this skill does not do
- Choose the shoot, write the shot outline, buy the licence or make taste calls. Present candidates and frames; the user decides.
- Record the real application.
- Compose music or voice.