vid-sizzle-reel — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited vid-sizzle-reel (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
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.
Generates a high-energy sizzle reel or hype video from brand assets and key messages. Pipeline: HyperFrames HTML composition (GSAP timelines) → headless Chromium → FFmpeg H.264 MP4. No Runway. No Pika. No AI video APIs. Zero runtime cost beyond Node.js + FFmpeg.
Differentiation from `vid-product-launch`:
vid-product-launch tells a narrative story — one earned reveal, five sections, tension arcvid-sizzle-reel creates energy and excitement — fast cuts, music-first, no single story beat// RHYTHM: flash-sequence | Cold[0-5s]: stat | Build[5-40s]: msg(1.5s)|flash|... | Peak[40-55s]: ... | Land[55-60s]: logo+CTA. Every timing decision checks against this.gsap.from() and exits with gsap.to(). Never position at animated start state.tl.play().width:100%; height:100%; padding:Npx; display:flex. Reserve position:absolute for decorative elements only.muted playsinline. Never put audio in a <video> element.Required:
key_messages — 3-5 punchy lines to flash on screen (user must write these)Optional parameters and defaults:
| Parameter | Default | Description |
|---|---|---|
| brand_assets | none | Logo URL/path, brand colors (hex), key screenshots |
| tone | energetic | energetic / cinematic / emotional / professional |
| music | none | File path, or BPM/genre string (e.g. "128bpm electronic") |
| duration | 60 | 30 / 60 / 90 seconds |
| aspect_ratio | 9:16 | 9:16 (1080x1920) / 16:9 (1920x1080) |
| cut_style | auto | fast (1-2s) / cinematic (3-5s) — auto derives from tone |
| end_card | auto | Logo + tagline + CTA URL for Land section |
cut_style auto-defaults: energetic → fast; professional → fast (2s); cinematic → cinematic; emotional → mixed
If `key_messages` is missing, ask exactly:
"To generate the sizzle reel, I need your key messages — the exact phrases that will flash on screen. Give me 3-5 punchy lines. These are the most important thing you're writing for this video.
>
Examples: 'Used by 500+ growth teams' / 'From days to minutes' / 'Works with Claude, Codex, Gemini'
>
Also useful: tone (energetic/cinematic/emotional/professional), duration (30/60/90s), any brand colors or logo URL."
If key_messages are present → proceed to Step 2 immediately.
Install HyperFrames skills (first time only — skip if already installed):
npx skills add heygen-com/hyperframesVerify environment:
node --version # must be >= 22
ffmpeg -version # must be presentScaffold the project:
npx hyperframes init sizzle-[slug] --example kinetic-type --non-interactive
cd sizzle-[slug]Slug: kebab-case from end_card brand name or first key_message, max 25 chars.
1. Derive cut-point timing from `duration` + `cut_style`:
| Section | 30s | 60s | 90s |
|---|---|---|---|
| Cold Open | 0-5s | 0-5s | 0-8s |
| Build | 5-25s | 5-40s | 8-65s |
| Peak | 25-28s | 40-55s | 65-80s |
| Land | 28-30s | 55-60s | 80-90s |
Convert every boundary to milliseconds. Calculate per-scene duration from cut_style:
fast: 1.2s-1.8s per message (use 1.5s default)cinematic: 3.5s-5.0s per message (use 4.0s default)mixed: 2.0s-4.0s varying by section energy2. Map key_messages to scenes:
3. Detect beat sync if music provided:
npx hyperframes tts --analyze-beats [music-file]If unavailable, calculate from BPM: beat_ms = 60000 / BPM. Align cuts to every 2nd beat.
4. Select tone preset — read references/tone-presets.md for CSS tokens.
5. Declare rhythm pattern — write comment at top of script:
// RHYTHM: flash-sequence
// Cold Open [0-5000ms]: stat
// Build [5000-40000ms]: msg-1(1500ms)|flash|msg-2(1500ms)|flash|msg-3(1500ms)|flash|msg-4(1500ms)|flash|build-accum(10000ms)
// Peak [40000-55000ms]: msg-strongest(1200ms)|flash|msg-2nd(1200ms)|flash|HOLD(5600ms)
// Land [55000-60000ms]: logo+tagline+cta6. Determine pixel dimensions:
9:16 → W=1080, H=1920 (default — Instagram Reels, TikTok, YouTube Shorts)16:9 → W=1920, H=1080Read references/cut-patterns.md and the HyperFrames skill (/hyperframes) before writing any code.
Required HTML skeleton:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[font CDN link from tone preset]
<style>
:root {
[all CSS tokens from tone preset]
}
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
</style>
</head>
<body>
<div
data-composition-id="sizzle-[slug]"
data-width="[W]"
data-height="[H]"
style="position:relative;width:[W]px;height:[H]px;overflow:hidden;background:var(--bg)"
>
[if cut-flash: true]
<div id="cut-flash"
style="position:absolute;inset:0;background:var(--cut-flash-bg);opacity:0;pointer-events:none;z-index:200"></div>
[end if]
[if film-grain: true]
<canvas id="grain-overlay"
width="240" height="135"
style="position:absolute;inset:0;width:[W]px;height:[H]px;pointer-events:none;opacity:0.022;mix-blend-mode:overlay;z-index:50"></canvas>
[end if]
[if vignette: true]
<div id="vignette-overlay"
style="position:absolute;inset:0;background:radial-gradient(ellipse at center,transparent 35%,rgba(0,0,0,0.65) 100%);pointer-events:none;z-index:51"></div>
[end if]
<!-- Scenes — one div per scene -->
<div id="scene-cold"
class="clip"
data-start="0"
data-duration="5"
data-track-index="0"
style="position:absolute;inset:0;opacity:0"
>
<div class="scene-content" style="width:100%;height:100%;padding:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;box-sizing:border-box">
[cold open content — one stat or impact phrase, no brand name]
</div>
</div>
[one div per Build scene, data-start increments by cut duration]
[Peak scene(s)]
[Land scene — brand name + tagline + CTA]
[if music provided]
<audio id="music"
class="clip"
data-start="0"
data-duration="[total_duration]"
data-track-index="10"
data-volume="0.85"
src="[music_path]"
></audio>
[end if]
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
// ── RHYTHM DECLARATION ──────────────────────────────────────────────────────
// RHYTHM: [flash-sequence | cinematic-hold | beat-sync | mixed]
// Cold Open [0-Nms]: ...
// Build [N-Nms]: ...
// Peak [N-Nms]: ...
// Land [N-Nms]: ...
// ── SEEDED PRNG (for grain only — no other random values) ───────────────────
function mulberry32(seed) {
return function() {
seed |= 0; seed = seed + 0x6D2B79F5 | 0;
let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
return ((t ^ t >>> 14) >>> 0) / 4294967296;
};
}
[if film-grain: true]
// ── FILM GRAIN (cinematic/emotional only) ───────────────────────────────────
const grainCanvas = document.getElementById('grain-overlay');
const grainCtx = grainCanvas ? grainCanvas.getContext('2d') : null;
// Grain updated by GSAP ticker (not per-frame capture — HyperFrames handles this)
[end if]
// ── GSAP TIMELINE ───────────────────────────────────────────────────────────
const tl = gsap.timeline({ paused: true });
// ── COLD OPEN ───────────────────────────────────────────────────────────────
[cold open entrance and exit tweens — see cut-patterns.md for exact pattern]
// ── BUILD SECTION ───────────────────────────────────────────────────────────
[per-message tweens — flash-sequence or cinematic-hold from cut-patterns.md]
// ── PEAK SECTION ─────────────────────────────────────────────────────────────
[peak tweens — same pattern as build but tighter timing]
// ── LAND SECTION ─────────────────────────────────────────────────────────────
[land entrances — logo, tagline, CTA. No exit — holds to end]
// ── TIMELINE REGISTRATION ────────────────────────────────────────────────────
window.__timelines = window.__timelines || {};
window.__timelines["sizzle-[slug]"] = tl;
</script>
</div>
</body>
</html>Design quality rules:
var(--accent) literal hex inside canvas fillStylescene-content always uses padding + flex — never position:absolute on content containerclass="clip" on every element with data-startContent:
key_messages — each phrase appears exactly once, no duplicatesHyperFrames contract:
class="clip" on every element with data-startdata-start, data-duration, data-track-index on every clipdata-composition-id on the root composition divwindow.__timelines["sizzle-[slug]"] = tl registered{ paused: true }Math.random() — seeded PRNG onlytl.play() or tl.resume() callsGSAP / Layout:
gsap.from() for all entrances (FROM offscreen TO CSS position)gsap.to() for all exits (FROM CSS position TO offscreen)position:absolute on .scene-content containersCut timing:
fast tone: average cut duration ≤ 2.0scinematic tone: average cut duration ≥ 3.0senergetic tone (60ms, opacity 0→1→0)data-track-index groupings)SFX:
window.__sfxTimeline present and placed before timeline registrationword-hit event per key messagewhoosh at Build → Peak transition boundarycta-chime at Land startnpx hyperframes lint
npx hyperframes inspect --jsonFix all lint errors. Fix all inspect errors (text overflow, clip escaping containers). Then render:
npx hyperframes render --output sizzle/[slug]/sizzle-reel.mp4Output:
## Sizzle Reel: [brand name from end_card]
Tone: [tone] | Duration: [N]s | Cut style: [cut_style] | Aspect: [ratio]
Rhythm: [declared pattern]
Sections
Cold Open [0-Ns]: [impact stat or phrase]
Build [N-Ns]: [N key messages listed]
Peak [N-Ns]: [strongest message at max scale]
Land [N-Ns]: [brand name] + "[tagline]" + [CTA URL]
Files
Source: sizzle/[slug]/index.html
Output: sizzle/[slug]/sizzle-reel.mp4The same FFmpeg-synthesized SFX system as vid-product-launch. Place window.__sfxTimeline immediately before the GSAP timeline registration.
SFX type reference for sizzle reel:
| Type | Sound | Duration | Placement |
|---|---|---|---|
word-hit | Sub punch (50Hz) + transient click (2.2kHz) + noise burst | 180ms | One per key_message scene start |
whoosh | Two-band noise sweep (1.1kHz body + 4-8kHz air) | 700ms | At Build → Peak boundary |
cta-chime | A major chord (440+554+659+880Hz) + aecho bell shimmer | 1.2s | Exactly at Land start |
window.__sfxTimeline = [
// one word-hit per key_message (computed from scene start times)
{ ms: MSG_1_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_2_START_MS, sfx: 'word-hit', vol: 0.50 },
{ ms: MSG_3_START_MS, sfx: 'word-hit', vol: 0.50 },
// ... repeat for each message
{ ms: PEAK_START_MS - 700, sfx: 'whoosh', vol: 0.55 },
{ ms: LAND_START_MS, sfx: 'cta-chime', vol: 0.65 },
];Compute all ms values from declared section timing constants. Never hardcode.
"Write the key messages yourself. They are the entire content of this video. 3 sharp lines beats 10 vague ones every time."
>
"Choose music before choosing visuals. The BPM determines the cut style. The cut style determines every timing decision."
>
"Cold open: one number. Not a sentence, not a tagline — one specific, surprising number. It earns 5 seconds of attention."
>
"The Land section is not a credits page. Logo + one line + one URL. Anything more dilutes the CTA."
>
"Match tone to channel. Energetic for social and Product Hunt. Cinematic for B2C premium and investor decks. Professional for enterprise conferences. Emotional for crowdfunding."
Good:
Sizzle reel, 60 seconds. Tone: energetic. Key messages: ['AI skills, ready to install' /
'52+ skills across GTM, content, research' / 'Works with Claude, Codex, Gemini' /
'Zero setup. Instant value.']. Music: 128bpm electronic. Cut style: fast.
End card: OpenDirectory + 'AI skills, ready to install' + 'opendirectory.dev'. Aspect: 9:16.Bad:
hype video for our company~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.