/** * Hook register — the named 2-second opening patterns (scroll-stopping beats) * and the two emitters that resolve a pattern and place it on the clock. * PURE and deterministic: no I/O, no Date, no Math.random. Extracted verbatim * from `cinematography.ts` (roadmap Phase 3a), which re-exports every public * name, so importers are unchanged. */ /** * The named 2-second hook patterns: scroll-stopping opening beats. Order is * intentional and stable; callers may iterate {@link HOOK_PATTERN_IDS}. */ export const HOOK_PATTERN_IDS = [ 'black-to-light', 'silence-to-sound', 'reverse-motion', 'beat-drop', 'match-cut-in', 'whip-reveal', 'speed-ramp', 'first-person-rush', 'impact-freeze', 'title-burn-in', 'slow-reveal', 'snap-zoom', // Per-category Seedance skill hook libraries (Track A → Track B bridge), // append-only so the twelve generic ids above stay byte-stable. // 3d-cgi 'scale-reveal', 'particle-materialize', 'impossible-angle', 'kinetic-decompose', // cartoon 'smash-zoom', 'rubber-hose-stretch', 'color-pop-burst', 'fourth-wall-wink', // fight-scenes 'weapon-clash-spark', 'ground-slam-shockwave', 'blade-unsheath-flash', // anime-action 'speed-line-burst', 'power-up-aura', 'sakura-gust', 'cross-impact-flash', // music-video 'hi-hat-flash-cuts', 'synth-glitch', 'harmonic-color-shift', 'downbeat-reveal', // social-hook 'impossible-scale', 'half-reveal', 'wait-for-it', 'ladder-reveal', ] as const; export type HookPatternId = (typeof HOOK_PATTERN_IDS)[number]; const HOOK_PATTERNS: Record = { 'black-to-light': 'Hard cut from black to a blinding light burst that resolves into the hero subject, irises adjusting as detail floods in.', 'silence-to-sound': 'Dead silence over a held still frame, then a sudden full-bandwidth sound hit lands as the image snaps into motion.', 'reverse-motion': 'Action plays in eerie reverse — debris, liquid, and fabric rushing back into place — before it whips forward into real time.', 'beat-drop': 'Rapid pre-roll build with quick-cut teases that freeze on the downbeat, then explode into the full scene on the drop.', 'match-cut-in': 'A graphic match cut carries a shape, motion, or color straight from an everyday object into the hero subject in one seamless jump.', 'whip-reveal': 'A fast whip-pan smears the frame into motion blur, then decelerates hard to reveal the hero subject dead-center.', 'speed-ramp': 'Action ramps from slow-motion into real time on a single continuous move, time compressing as the hero subject commits.', 'first-person-rush': 'A first-person rush hurtles forward through the environment, motion close and visceral, before braking hard on the hero subject.', 'impact-freeze': 'The frame slams to a freeze on the exact instant of impact, debris suspended mid-air, then releases back into motion.', 'title-burn-in': 'A single word burns in from particulate or light, holds for a beat, then dissolves as the scene takes over.', 'slow-reveal': 'A slow tilt or pull gradually uncovers the hero subject from an obscuring foreground element, withholding then granting the full view.', 'snap-zoom': 'A fast snap-zoom punches from a wide to a tight frame on the hero subject, landing hard with no settle.', // --- 3d-cgi --- 'scale-reveal': 'A continuous scale reveal rockets from a microscopic detail out to colossal scale, the hero subject snapping into frame as the magnitude lands.', 'particle-materialize': 'The hero subject assembles out of a swirling cloud of particles that rush inward and lock into solid form on the beat.', 'impossible-angle': 'An impossible camera angle — clipping through solid geometry or defying gravity — sweeps to frame the hero subject from a vantage no real rig could hold.', 'kinetic-decompose': 'The hero subject explodes into its component parts mid-air, fragments suspended and rotating, then snaps back together into one solid form.', // --- cartoon --- 'smash-zoom': 'The character smash-zooms straight into the lens with a cartoon impact frame — eyes bulging, motion-blur trails streaking — then bounces back to a settle.', 'rubber-hose-stretch': 'The subject squashes and stretches in exaggerated rubber-hose motion, anticipation winding up before it springs into the scene.', 'color-pop-burst': 'A flat scene detonates into saturated color, shapes and confetti bursting outward from the hero subject on the beat.', 'fourth-wall-wink': 'The character turns to camera, breaks the fourth wall with a knowing wink or grin, then spins back into the action.', // --- fight-scenes --- 'weapon-clash-spark': 'Two weapons collide dead-center frame in a radial burst of sparks, the camera kicking back as the shockwave rings out.', 'ground-slam-shockwave': 'A combatant slams into the earth and a visible shockwave radiates outward in a perfect ring, dust kicking up as a second fighter staggers.', 'blade-unsheath-flash': 'A blade is drawn in a single fluid motion, catching the light in a brilliant flash with the metallic ring of steel.', // --- anime-action --- 'speed-line-burst': 'Hard radial speed lines burst from the center as the hero subject snaps into a power pose, the frame vibrating with kinetic energy.', 'power-up-aura': 'A surging aura erupts around the subject, ground cracking and debris lifting as the energy spikes and the frame floods with light.', 'sakura-gust': 'A gust of cherry-blossom petals sweeps across frame, parting to reveal the hero subject mid-stride in still, charged air.', 'cross-impact-flash': 'The frame freezes on a single impact frame — a bright cross-shaped highlight flaring at the point of contact — held for a beat before motion resumes.', // --- music-video --- 'hi-hat-flash-cuts': 'Rapid jump cuts fire on every hi-hat hit, snapping between framings with rhythm-matched light flashes.', 'synth-glitch': 'On the synth stab the image glitches — horizontal tearing, frame lag, and a hard color shift — before resolving back to the performer.', 'harmonic-color-shift': 'The entire palette shifts on the harmonic change, one chord melting the grade into a new dominant color as the scene holds.', 'downbeat-reveal': 'An empty scene holds through the build, then cuts hard to the performer in sharp focus exactly on the downbeat.', // --- social-hook --- 'impossible-scale': 'An object or subject appears at an impossible scale — far too large or too small for its setting — forcing a double-take in the first second.', 'half-reveal': 'The hero subject is only half-shown — cropped, occluded, or just out of frame — withholding the full view to bait the scroll-stopper.', 'wait-for-it': 'A mundane setup holds with deliberate tension and an implied promise that something is about to happen, daring the viewer not to wait.', 'ladder-reveal': 'A rapid escalating ladder of quick reveals, each one topping the last, ratcheting curiosity higher with every cut.', }; /** * Pad an integer second count to a 2-digit string (assumes < 60). */ function padSeconds(seconds: number): string { return String(seconds).padStart(2, '0'); } /** * Resolve a named opening-hook pattern id to its directive description. * * Like {@link hookBeat}, an unknown id THROWS rather than falling back — hooks * must be explicit. Exported so CLI surfaces can prepend the directive without * re-deriving the table. */ export function resolveHookPattern(pattern: HookPatternId): string { const description = HOOK_PATTERNS[pattern]; if (!description) { throw new Error(`unknown hook pattern: ${pattern}`); } return description; } /** * Render a named 2-second opening hook as a timecoded beat. * * Returns `"[00:00 - 00:0N] "` where `N` is `hookSeconds` * (zero-padded, assumed < 60). Unlike the cinema-mode resolvers, an unknown * pattern id THROWS rather than falling back — hooks must be explicit. */ export function hookBeat(pattern: HookPatternId, hookSeconds: number): string { const description = HOOK_PATTERNS[pattern]; if (!description) { throw new Error(`unknown hook pattern: ${pattern}`); } return `[00:00 - 00:${padSeconds(hookSeconds)}] ${description}`; }