flat-artist is now the single container: all 16 template skills + the R&D references/ moved inside flat-artist/. Cross-references updated — the orchestrator points to bundled `<name>/SKILL.md`, sub-skills point to `../<name>/SKILL.md`, and the R&D report path is relative. README catalog updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.7 KiB
name, description
| name | description |
|---|---|
| video-hooks | How to design the scroll-stopping first 1-3 seconds of a FlatRender Remotion template — hook archetypes, pattern interrupts, on-screen text hooks, curiosity gaps, and platform-specific (Instagram/TikTok/YouTube) hook norms — and bake them into the template's opening beats. Use whenever building or reviewing a template's first frames, the cover/first frame, the caption hook layer, or retention pacing of the open. |
The hook (first 1-3 seconds — where templates are won or lost)
On a 9:16 feed the viewer decides stay or swipe in 2-3 seconds (TikTok's "3-second rule"; IG rewards 3-sec view rate). YouTube Shorts has no runway — open on the most compelling moment. So a FlatRender template doesn't get a polite logo intro: the first frame is the cover/thumbnail and the hook, and the first ~45-90 frames (@30fps) must arrest the eye. Everything here is a pure function of useCurrentFrame() — no Math.random/Date.now/useFrame; use rand(seed) from lib/anim.ts. Read ../remotion-aspect-ratios/SKILL.md before positioning a single hook element.
The frame budget for the open (30fps; use sec(s)=Math.round(s*fps))
| Beat | Frames | Job |
|---|---|---|
| f0 — cover | 1 | Must already read as a finished, intriguing thumbnail. No black/empty frame 0. |
| Pattern interrupt | 0-12 | One bold motion/sound jolt that breaks the scroll rhythm. |
| Hook text lands | 6-30 | The promise/question/claim, big, high-contrast, lower-middle third. |
| Curiosity hold | 30-75 | Pose an open loop the rest of the video closes. Don't resolve yet. |
| Hero handoff | 60-90 | Flow into logo/headline (../remotion-template-composition/SKILL.md). |
Front-load the payoff — no preamble, no slow brand sting first. Brand comes after the hook earns the watch.
Hook archetypes (Persian-first copy; pick ONE per template)
| Archetype | Persian opener pattern | Best for | Motion signature |
|---|---|---|---|
| Curiosity gap | «اینو تا آخر ببین…» / «هیچکس اینو بهت نگفته» | tips, reveals, teasers | text snaps in, then a held pause (open loop) |
| Bold claim / contrarian | «این روش رو فراموش کن» / «۹۰٪ اشتباه انجامش میدن» | how-to, product | hard cut + overshoot back-bezier |
| Question | «دنبال … میگردی؟» | services, lead-gen | rise + tilt, then steady |
| Negativity / warning | «این اشتباه رو نکن» | finance, health, safety | red accent flash + shake |
| Number / list | «۳ دلیل که…» / «۵ نکته…» | listicles, carousels | counter ticks up, items pre-stack off-screen |
| Result-first | show the after/price-drop/win immediately | promo, sale, before-after | hero appears f0, then explains |
| Direct address | «تو که … هستی، اینو لازم داری» | niche/targeted | type fills 70-90% of frame |
Use Persian numerals (۰-۹) — never Latin digits — in hook copy and counters; fa is source of truth, en mirrors 1:1.
Pattern interrupts (the scroll-breaking jolt in f0-12)
The feed has a rhythm; a hook breaks it. Stack 1-2 of these, never all:
- Motion jolt — whip-in with overshoot:
Easing.bezier(0.34,1.56,0.64,1), or a low-dampingspring({mass:0.6,damping:9,stiffness:200}). Add motion blur on the fast frames (its absence is an amateur tell). - Hard cut + flash — a 1-2 frame white/accent wash:
opacity = frame < 2 ? 1 : 0over ahexToRgba(accentColor, …)fill. Pair with a thump SFX (../remotion-sound-effects/SKILL.md). - Scale punch — start at
scale1.6→1.0 (clamp) so the subject "slams" toward camera. - Color shock — open on a dopamine accent (electric blue/coral/acid) on a neutral base; pull it from
accentColorso the studio recolors it. - Silence-then-hit — a held silent f0-8, then riser+downbeat on the hook (
../remotion-music-picker/SKILL.mdBPM map). The pause is the interrupt.
// Pattern-interrupt whip-in for the hook line (deterministic, clamped)
const f = useCurrentFrame();
const { fps } = useVideoConfig();
const intro = spring({ frame: f, fps, config: { mass: 0.6, damping: 9, stiffness: 200 } });
const y = interpolate(intro, [0, 1], [L.vmin(60), 0]); // rises into place
const flash = interpolate(f, [0, 2, 5], [1, 0.5, 0], { extrapolateRight: "clamp" });
On-screen text hooks (the highest-ROI layer)
The hook text is a first-class editable field, not decoration — it is the captions/cover layer the whole brief calls the biggest cross-platform win.
- Placement: lower-middle third, inside the tightest safe zone (Story/TikTok) so it's safe everywhere. For 1080×1920 keep hook Y ≈
height*0.18-0.55; clear top ~108 and bottom ~320 (UI chrome). - Legibility: high-contrast white or acid-yellow fill + black outline (
WebkitTextStrokeor layeredtextShadow), never thin grey on busy bg. Add a scrim if over media. - Oversized & clipped: the hook word can fill 60-90% of frame (
fitTextfrom@remotion/layout-utils); clip withoverflow:hidden. Strongest on 9:16. - Kinetic / word-by-word: beats full sentences on TikTok. Split to spans,
delay = i*stagger, drive each withspring({frame: f - delay, fps}). Stagger looser on tall, tighter on wide viapick. - Variable weight pop: Vazirmatn ships a variable build — animate
fontVariationSettings: "'wght' " + interpolate(f,[0,12],[300,900])for a Persian hero hook.
// Word-by-word Persian hook, RTL, outlined, beat-staggered
const words = hookText.split(" ");
const stagger = L.pick(2, 3, 4); // wide reads faster → tighter
return (
<div style={{ direction: "rtl", fontFamily: FONT, display: "flex",
gap: L.vmin(8), justifyContent: "center", flexWrap: "wrap",
maxWidth: L.width * 0.86 }}>
{words.map((w, i) => {
const s = spring({ frame: f - i * stagger, fps, config: { damping: 12 } });
return (
<span key={i} style={{
fontSize: L.pick(L.vmin(96), L.vmin(84), L.vmin(72)), fontWeight: 900,
color: textColor, WebkitTextStroke: `${L.vmin(6)}px ${BRAND.ink}`,
paintOrder: "stroke", transform: `translateY(${(1 - s) * L.vmin(40)}px)`,
opacity: s,
}}>{w}</span>
);
})}
</div>
);
Curiosity & retention pacing across the open
- Open a loop, close it later — the hook promises, the hero pays off. Never resolve the question in the first 2s or there's no reason to stay.
- One idea per beat — staging: dim/blur everything but the hook; let it own the eye before the next element competes.
- Hold for the read — a hook line needs ~0.6-0.8s minimum on screen before motion competes. Robotic = linear; floaty = held too long. Cut frames before adding.
- Tiny life in the hold — a
sin(f/fps)breathe/shimmer so the held hook isn't a frozen frame. - Grain + texture from f0 — even the cover frame should have animated grain (offset
background-positionper frame); flat-saturated = reads as AI/template.
Platform hook norms → template implication
| Platform | Hook window | Norm | Template move |
|---|---|---|---|
| TikTok | 3s | curiosity-gap / bold-claim; word-by-word captions | calm neutral grain + warm-earth variant; word-by-word hook as editable layer |
| IG Reels | 2-3s | cleaner, less-cluttered than TikTok | refined kinetic type, glass lower-third, mesh-gradient bg, one clean interrupt |
| YT Shorts | f0 | no runway — open on the peak | result-first / hero-at-f0; cinematic graded look |
| YT long-form intro | 5-15s | cold-open hook, brand sting <3s | state payoff first, brand second |
| IG Story | full-bleed | heavy UI chrome | keep hook clear of top ~250 / bottom ~250 |
| All three | 1-2s | first frame = hook = cover; authenticity > gloss | hook prop in every aspect, re-flowed not letterboxed |
Tie the hook into template structure
- Make the hook copy a Zod prop (e.g.
hookText: z.string()) + a seededTextelement whosekeymatches — same binding model as../remotion-template-composition/SKILL.md. Ship strong Persian default copy so it reads finished pre-edit. - Hook color =
accentColor/textColorfromcolorSchema; pass user hex through a grade so a garish value doesn't break the open (../remotion-svg-colors/SKILL.md). - The hook is a
<Sequence from={0} durationInFrames={sec(2.5)}>; the hero sequence overlaps its tail so the handoff is a flow, not a cut. - 3D hooks: keep the interrupt object filling the frame per aspect (tune
fov/position.z), drive entrance fromuseCurrentFrame()with highmassfor weight; letStudioEffects(bloom/DOF/vignette) finish it.
Hook checklist (gate the open)
- Frame 0 reads as a finished, intriguing cover — no black/empty/half-loaded frame.
- A single clear pattern interrupt in f0-12 (motion / flash / scale / color / silence-then-hit) with SFX.
- ONE hook archetype; Persian-first copy with Persian numerals;
enmirror present. - Hook text is an editable prop, high-contrast + outlined, in the tightest safe zone, no clipping with long Persian strings.
- An open loop is posed and NOT resolved in the first 2s; payoff lands at the hero.
- Eased/overshoot motion (no linear), held for the read, with a tiny live shimmer; animated grain from f0.
- Verified the open in all three aspects (
pick-tuned), recolors cleanly, re-renders identical (deterministic).
Related: ../remotion-template-composition/SKILL.md, ../remotion-aspect-ratios/SKILL.md, ../remotion-design-styles/SKILL.md, ../remotion-sound-effects/SKILL.md, ../remotion-music-picker/SKILL.md, ../remotion-svg-colors/SKILL.md, ../persian-fonts/SKILL.md, ../remotion-template-catalog/SKILL.md, ../flatrender-template-seo/SKILL.md.