Files
flatrender/.claude/skills/flat-artist/video-hooks/SKILL.md
T
soroush.asadi f83d657844
CI/CD / CI · Web (tsc) (push) Successful in 1m19s
CI/CD / Deploy · full stack (push) Failing after 12s
chore(skills+remotion): add flat-artist skill bundle; register 3D templates
- .claude/skills/flat-artist: the bundled FlatRender template-creation suite
  (orchestrator + 16 sub-skills + design/motion R&D), mirrors the Gitea AISkills repo.
- services/remotion Root.tsx/templates.tsx: register the 3D templates + Three3DTest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 19:39:25 +03:30

115 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: video-hooks
description: 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-damping `spring({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 : 0` over a `hexToRgba(accentColor, …)` fill. Pair with a thump SFX (`../remotion-sound-effects/SKILL.md`).
- **Scale punch** — start at `scale` 1.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 `accentColor` so the studio recolors it.
- **Silence-then-hit** — a held silent f0-8, then riser+downbeat on the hook (`../remotion-music-picker/SKILL.md` BPM map). The pause *is* the interrupt.
```tsx
// 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** (`WebkitTextStroke` or layered `textShadow`), never thin grey on busy bg. Add a scrim if over media.
- **Oversized & clipped:** the hook word can fill 60-90% of frame (`fitText` from `@remotion/layout-utils`); clip with `overflow:hidden`. Strongest on 9:16.
- **Kinetic / word-by-word:** beats full sentences on TikTok. Split to spans, `delay = i*stagger`, drive each with `spring({frame: f - delay, fps})`. Stagger looser on tall, tighter on wide via `pick`.
- **Variable weight pop:** Vazirmatn ships a variable build — animate `fontVariationSettings: "'wght' " + interpolate(f,[0,12],[300,900])` for a Persian hero hook.
```tsx
// 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-position` per 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 seeded `Text` element whose `key` matches — same binding model as `../remotion-template-composition/SKILL.md`. Ship strong Persian default copy so it reads finished pre-edit.
- Hook color = `accentColor`/`textColor` from `colorSchema`; 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 from `useCurrentFrame()` with high `mass` for weight; let `StudioEffects` (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; `en` mirror 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`.