diff --git a/.gitignore b/.gitignore index 983925dfd..037b814d3 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ Thumbs.db # non-generated assets (logos, svgs) that should stay in the repo. docs/images/ +videos/ + # IDE .vscode/ .idea/ @@ -74,6 +76,7 @@ examples/* !examples/k8s-jobs !examples/k8s-jobs/** packages/studio/data/ + .desloppify/ .worktrees/ @@ -103,10 +106,22 @@ captures/ cursor-tests/ basecamp-video/ launch-video*/ +!skills/launch-video/ ab-test/ compositions/ video-6-2-patched/ claude-design-hyperframes-video/ +# Per-site video work at the repo root (huly-*, raycast-*, etc.) +# Anything under videos/ is already covered above, but agents sometimes write +# project dirs to the repo root when iterating. Catch the common per-brand +# patterns and any *-demo-N variants the *-demo/ rule above misses. +huly-*/ +raycast-*/ +*-demo-*/ +test-runs/ +test-outputs/ + +# Claude Code worktrees + superpowers docs .claude/worktrees/ .claude/ docs/superpowers/ diff --git a/skills/hyperframes/SKILL.md b/skills/hyperframes/SKILL.md index f68c52dac..2ec610cb4 100644 --- a/skills/hyperframes/SKILL.md +++ b/skills/hyperframes/SKILL.md @@ -473,7 +473,8 @@ Skip on small edits (fixing a color, adjusting one duration). Run on new composi - **[references/beat-direction.md](references/beat-direction.md)** — Beat planning: concept, mood, choreography verbs, rhythm templates, transition decisions, depth layers. **Always read for multi-scene compositions.** - **[references/typography.md](references/typography.md)** — Typography: font pairing, OpenType features, dark-background adjustments, font discovery script. **Always read** — every composition has text. - **[references/motion-principles.md](references/motion-principles.md)** — Motion design principles, image motion treatment, load-bearing GSAP rules. **Always read** — every composition has motion. -- **[references/techniques.md](references/techniques.md)** — 11 visual techniques with code patterns: SVG drawing, Canvas 2D, CSS 3D, kinetic type, Lottie, video compositing, typing effect, variable fonts, MotionPath, velocity transitions, audio-reactive. Read when planning techniques per beat. +- **[references/techniques.md](references/techniques.md)** — 13 primitive animation techniques with code patterns: SVG drawing, Canvas 2D, CSS 3D, kinetic type, Lottie, video compositing, typing, variable fonts, MotionPath, velocity transitions, audio-reactive, clip-path reveals, WebGL shaders. Adapt the patterns — don't copy-paste. (For pre-built UI templates — terminal chrome, device mockups, moodboard layouts — see `registry/blocks/`.) +- **[references/html-in-canvas-patterns.md](references/html-in-canvas-patterns.md)** — HTML-in-Canvas patterns: live DOM as GPU texture via `drawElementImage` + `layoutsubtree`. Shared boilerplate + ~6 effect recipes (iPhone/MacBook mockups, liquid glass, magnetic, portal, shatter, text cursor). Use for 1–3 hero beats per video. - **[references/narration.md](references/narration.md)** — Pacing, tone, script structure, number pronunciation, opening line patterns. Read when the composition includes voiceover or TTS. - **[references/design-picker.md](references/design-picker.md)** — Create a design.md via visual picker. Read when no design.md exists and the user wants to create one. - **[visual-styles.md](visual-styles.md)** — 8 named visual styles with hex palettes, GSAP easing signatures, and shader pairings. Read when user names a style or when generating design.md. diff --git a/skills/hyperframes/assets/text-effects/blur-out-up.json b/skills/hyperframes/assets/text-effects/blur-out-up.json new file mode 100644 index 000000000..33885a63b --- /dev/null +++ b/skills/hyperframes/assets/text-effects/blur-out-up.json @@ -0,0 +1,22 @@ +{ + "id": "blur-out-up", + "name": "Blur Out Up", + "description": "Words arrive clean and exit upward with increasing blur. The entrance is matter-of-fact; the exit dissolves into atmosphere. Asymmetric pairing — good when the line should feel like it lingers in the viewer's memory rather than getting decisively dismissed.", + "target": "word", + "enter": { + "durationMs": 360, + "staggerMs": 90, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "y": 6 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 520, + "staggerMs": 22, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 1, "y": 0, "filter": "blur(0px)" }, + "to": { "opacity": 0, "y": -28, "filter": "blur(12px)" } + }, + "swap": { "mode": "crossfade", "overlapMs": 200, "microDelayMs": 0 }, + "notes": "The 200ms overlap during swap is intentional — incoming text starts arriving while outgoing text is still mid-blur. Reads as a transition rather than a clean cut." +} diff --git a/skills/hyperframes/assets/text-effects/bottom-up-letters.json b/skills/hyperframes/assets/text-effects/bottom-up-letters.json new file mode 100644 index 000000000..e91386b18 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/bottom-up-letters.json @@ -0,0 +1,22 @@ +{ + "id": "bottom-up-letters", + "name": "Bottom-Up Letters", + "description": "Letters rise from below in a pronounced staircase. Each character takes more visual time than per-character-rise — the motion is larger, slower, and reads as confident punctuation rather than ambient build.", + "target": "char", + "enter": { + "durationMs": 320, + "staggerMs": 65, + "easing": "cubic-bezier(0.18, 1, 0.32, 1)", + "from": { "opacity": 0, "y": 56 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 280, + "staggerMs": 14, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": -22 } + }, + "swap": { "mode": "crossfade", "overlapMs": 80, "microDelayMs": 0 }, + "notes": "Pair with bold or display-weight headlines. The 65ms stagger creates an audible-feeling rhythm; works well when the headline lands on a beat marker in narration." +} diff --git a/skills/hyperframes/assets/text-effects/depth-parallax-words.json b/skills/hyperframes/assets/text-effects/depth-parallax-words.json new file mode 100644 index 000000000..bf8d0b4ca --- /dev/null +++ b/skills/hyperframes/assets/text-effects/depth-parallax-words.json @@ -0,0 +1,22 @@ +{ + "id": "depth-parallax-words", + "name": "Depth Parallax Words", + "description": "Per-word entrance where each word enters at a different scale and slight vertical offset, simulating depth — back words start smaller and lower, front words larger and at baseline. Reads as a 3D-feeling layered headline without needing a real Z-axis transform.", + "target": "word", + "enter": { + "durationMs": 540, + "staggerMs": 110, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "y": 18, "scale": 0.82 }, + "to": { "opacity": 1, "y": 0, "scale": 1 } + }, + "exit": { + "durationMs": 360, + "staggerMs": 22, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0, "scale": 1 }, + "to": { "opacity": 0, "y": -8, "scale": 0.92 } + }, + "swap": { "mode": "crossfade", "overlapMs": 120, "microDelayMs": 0 }, + "notes": "The scale + y combination is what reads as depth. Don't drop scale below 0.7 — at that point the word looks small rather than far. Keep above 0.8 for the parallax illusion to hold." +} diff --git a/skills/hyperframes/assets/text-effects/fade-through.json b/skills/hyperframes/assets/text-effects/fade-through.json new file mode 100644 index 000000000..5cf187d92 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/fade-through.json @@ -0,0 +1,20 @@ +{ + "id": "fade-through", + "name": "Fade Through", + "description": "Material-style cross-dissolve where the old content fades out completely before the new content fades in. Different from a normal crossfade — there's a brief moment where neither is visible. Reads as a contextual swap rather than a continuous flow.", + "target": "element", + "enter": { + "durationMs": 280, + "easing": "cubic-bezier(0.2, 0, 0, 1)", + "from": { "opacity": 0 }, + "to": { "opacity": 1 } + }, + "exit": { + "durationMs": 200, + "easing": "cubic-bezier(0.4, 0, 1, 1)", + "from": { "opacity": 1 }, + "to": { "opacity": 0 } + }, + "swap": { "mode": "sequential", "overlapMs": -60, "microDelayMs": 0 }, + "notes": "The -60ms overlap is intentional: outgoing element finishes fading out, then 60ms of empty space, then incoming starts. The empty moment is what distinguishes this from a crossfade. Implementer: timeline puts the new content's enter AFTER the previous content's exit completes." +} diff --git a/skills/hyperframes/assets/text-effects/focus-blur-resolve.json b/skills/hyperframes/assets/text-effects/focus-blur-resolve.json new file mode 100644 index 000000000..b0b301ff4 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/focus-blur-resolve.json @@ -0,0 +1,20 @@ +{ + "id": "focus-blur-resolve", + "name": "Focus Blur Resolve", + "description": "Heavy blur resolves to sharp clarity on entrance, returns to soft blur on exit. Reads as a camera-focus pull — the element doesn't move; it just comes into focus. Cinematic, attention-pulling.", + "target": "element", + "enter": { + "durationMs": 580, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "filter": "blur(16px)" }, + "to": { "opacity": 1, "filter": "blur(0px)" } + }, + "exit": { + "durationMs": 420, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "filter": "blur(0px)" }, + "to": { "opacity": 0, "filter": "blur(6px)" } + }, + "swap": { "mode": "crossfade", "overlapMs": 160, "microDelayMs": 0 }, + "notes": "The 16px entrance blur is heavy enough to feel like out-of-focus rather than mild softness. Don't push above 24px — at that point the text is unreadable for too long. Don't drop below 8px — at that point the effect reads as 'gently fades in,' not 'focuses.'" +} diff --git a/skills/hyperframes/assets/text-effects/kinetic-center-build.json b/skills/hyperframes/assets/text-effects/kinetic-center-build.json new file mode 100644 index 000000000..77bf13875 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/kinetic-center-build.json @@ -0,0 +1,23 @@ +{ + "id": "kinetic-center-build", + "name": "Kinetic Center Build", + "description": "Each word locks to its center position as the phrase builds right-to-left with a soft blur. Layout-aware — the next word arrives at the previous word's left edge while the line stays centered in frame.", + "target": "word", + "enter": { + "durationMs": 480, + "staggerMs": 220, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "x": 24, "filter": "blur(6px)" }, + "to": { "opacity": 1, "x": 0, "filter": "blur(0px)" } + }, + "exit": { + "durationMs": 420, + "staggerMs": 40, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0, "filter": "blur(0px)" }, + "to": { "opacity": 0, "y": -10, "filter": "blur(6px)" } + }, + "swap": { "mode": "crossfade", "overlapMs": 160, "microDelayMs": 0 }, + "layoutAware": true, + "notes": "Layout-aware: the implementer must shift each word's x-position so the whole phrase stays horizontally centered as it grows. Without this, words appended to the right push the line off-center. Computed positions, not just stagger timing — read the layout-aware section of text-effects.md." +} diff --git a/skills/hyperframes/assets/text-effects/line-by-line-slide.json b/skills/hyperframes/assets/text-effects/line-by-line-slide.json new file mode 100644 index 000000000..a00e62242 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/line-by-line-slide.json @@ -0,0 +1,22 @@ +{ + "id": "line-by-line-slide", + "name": "Line By Line Slide", + "description": "Each line slides in from the left, exits to the right. Reads as flowing paragraph rhythm — best for multi-line body copy or quotes where the lines build sequentially. Different from mask-reveal-up in that the motion is horizontal, not masked.", + "target": "line", + "enter": { + "durationMs": 640, + "staggerMs": 110, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "x": -40 }, + "to": { "opacity": 1, "x": 0 } + }, + "exit": { + "durationMs": 480, + "staggerMs": 60, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "x": 0 }, + "to": { "opacity": 0, "x": 32 } + }, + "swap": { "mode": "crossfade", "overlapMs": 160, "microDelayMs": 0 }, + "notes": "Best for 2–4 lines max. Beyond that the cumulative stagger gets noticeably long (110ms × 5 lines = 550ms before the last line starts moving). For long body copy, prefer mask-reveal-up or shorten the stagger." +} diff --git a/skills/hyperframes/assets/text-effects/mask-reveal-up.json b/skills/hyperframes/assets/text-effects/mask-reveal-up.json new file mode 100644 index 000000000..36c5b68cb --- /dev/null +++ b/skills/hyperframes/assets/text-effects/mask-reveal-up.json @@ -0,0 +1,22 @@ +{ + "id": "mask-reveal-up", + "name": "Mask Reveal Up", + "description": "Each line of text reveals as a `clip-path` mask wipes upward — the text moves up into a fixed viewport. Reads as contained, intentional, and slightly magazine-like; the masked feel separates this from a plain fade.", + "target": "line", + "enter": { + "durationMs": 580, + "staggerMs": 90, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "y": 36, "clipPath": "inset(100% 0% 0% 0%)" }, + "to": { "y": 0, "clipPath": "inset(0% 0% 0% 0%)" } + }, + "exit": { + "durationMs": 420, + "staggerMs": 40, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "y": 0, "clipPath": "inset(0% 0% 0% 0%)" }, + "to": { "y": -12, "clipPath": "inset(0% 0% 100% 0%)" } + }, + "swap": { "mode": "crossfade", "overlapMs": 120, "microDelayMs": 0 }, + "notes": "Each `.line` element needs `overflow: hidden` on its parent so the clip-path mask reads as a window. Without that the y-translation just shows the text moving below its baseline — the masking effect is lost." +} diff --git a/skills/hyperframes/assets/text-effects/micro-scale-fade.json b/skills/hyperframes/assets/text-effects/micro-scale-fade.json new file mode 100644 index 000000000..26ef27008 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/micro-scale-fade.json @@ -0,0 +1,20 @@ +{ + "id": "micro-scale-fade", + "name": "Micro Scale Fade", + "description": "Whole-element entrance with a tiny scale change (0.96 → 1.00) and a fade. Barely perceptible — reads as polish rather than animation. Use when you want the element to settle in without drawing attention to the motion itself.", + "target": "element", + "enter": { + "durationMs": 460, + "easing": "cubic-bezier(0.32, 0.72, 0, 1)", + "from": { "opacity": 0, "scale": 0.96 }, + "to": { "opacity": 1, "scale": 1 } + }, + "exit": { + "durationMs": 320, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "scale": 1 }, + "to": { "opacity": 0, "scale": 0.98 } + }, + "swap": { "mode": "crossfade", "overlapMs": 140, "microDelayMs": 0 }, + "notes": "Don't push scale beyond 0.92 or below 0.94 — at that range the motion stops reading as polish and starts reading as 'something is shrinking.' The whole point is to feel deliberate without feeling animated." +} diff --git a/skills/hyperframes/assets/text-effects/per-character-rise.json b/skills/hyperframes/assets/text-effects/per-character-rise.json new file mode 100644 index 000000000..1c2373383 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/per-character-rise.json @@ -0,0 +1,22 @@ +{ + "id": "per-character-rise", + "name": "Per-Character Rise", + "description": "Letters slide up from below baseline with no blur, settling one after another. Crisp and deliberate — the structural answer to soft-blur-in.", + "target": "char", + "enter": { + "durationMs": 520, + "staggerMs": 18, + "easing": "cubic-bezier(0.2, 0.8, 0.2, 1)", + "from": { "opacity": 0, "y": 24 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 320, + "staggerMs": 8, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": -10 } + }, + "swap": { "mode": "crossfade", "overlapMs": 100, "microDelayMs": 0 }, + "notes": "Works well across most weights. The 24px rise distance gives obvious motion without crossing into theatrical territory." +} diff --git a/skills/hyperframes/assets/text-effects/per-word-crossfade.json b/skills/hyperframes/assets/text-effects/per-word-crossfade.json new file mode 100644 index 000000000..d2b9bdd4e --- /dev/null +++ b/skills/hyperframes/assets/text-effects/per-word-crossfade.json @@ -0,0 +1,22 @@ +{ + "id": "per-word-crossfade", + "name": "Per-Word Crossfade", + "description": "Words fade in one at a time with a short vertical drift. The rhythm is calm and sequential — best for sub-headlines, body copy that needs gentle pacing, or value-prop lines where each word should land before the next.", + "target": "word", + "enter": { + "durationMs": 460, + "staggerMs": 140, + "easing": "cubic-bezier(0.16, 1, 0.3, 1)", + "from": { "opacity": 0, "y": 10 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 320, + "staggerMs": 32, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": -6 } + }, + "swap": { "mode": "crossfade", "overlapMs": 140, "microDelayMs": 0 }, + "notes": "140ms stagger is the sweet spot for short headlines (3-7 words). For longer lines (8+ words) drop stagger to 80-100ms to keep the total entrance under 1.5 seconds." +} diff --git a/skills/hyperframes/assets/text-effects/scale-down-fade.json b/skills/hyperframes/assets/text-effects/scale-down-fade.json new file mode 100644 index 000000000..d0d0e4649 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/scale-down-fade.json @@ -0,0 +1,20 @@ +{ + "id": "scale-down-fade", + "name": "Scale Down Fade", + "description": "Content settles with a slight scale-down on entrance (1.04 → 1.00) and the same on exit. Restrained and premium — the scale gives the element a subtle 'arriving' and 'departing' feel without being theatrical.", + "target": "element", + "enter": { + "durationMs": 380, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "scale": 1.04 }, + "to": { "opacity": 1, "scale": 1 } + }, + "exit": { + "durationMs": 380, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 1, "scale": 1 }, + "to": { "opacity": 0, "scale": 0.96 } + }, + "swap": { "mode": "crossfade", "overlapMs": 140, "microDelayMs": 0 }, + "notes": "Symmetric durations (380ms in, 380ms out) keep the entrance and exit feeling like the same motion played in reverse. Asymmetric durations work for other effects but read as unsettled here." +} diff --git a/skills/hyperframes/assets/text-effects/shared-axis-x.json b/skills/hyperframes/assets/text-effects/shared-axis-x.json new file mode 100644 index 000000000..fca936a24 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/shared-axis-x.json @@ -0,0 +1,20 @@ +{ + "id": "shared-axis-x", + "name": "Shared Axis X", + "description": "Horizontal sibling transition — outgoing content slides left and fades, incoming content arrives from the right with a fade. Reads as 'next page' or 'sequential destination,' similar to Material's shared-axis-X pattern.", + "target": "element", + "enter": { + "durationMs": 380, + "easing": "cubic-bezier(0.2, 0, 0, 1)", + "from": { "opacity": 0, "x": 40 }, + "to": { "opacity": 1, "x": 0 } + }, + "exit": { + "durationMs": 380, + "easing": "cubic-bezier(0.4, 0, 1, 1)", + "from": { "opacity": 1, "x": 0 }, + "to": { "opacity": 0, "x": -40 } + }, + "swap": { "mode": "crossfade", "overlapMs": 180, "microDelayMs": 0 }, + "notes": "Use shared-axis-x for sequential moves (next/previous), shared-axis-y for hierarchical (up/down), shared-axis-z for contextual swaps (zoom in/out). Symmetric ±40px translates." +} diff --git a/skills/hyperframes/assets/text-effects/shared-axis-y.json b/skills/hyperframes/assets/text-effects/shared-axis-y.json new file mode 100644 index 000000000..78886c0c5 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/shared-axis-y.json @@ -0,0 +1,22 @@ +{ + "id": "shared-axis-y", + "name": "Shared Axis Y", + "description": "Hard-cut word-by-word with staircase timing along the Y axis. No interpolation — each word snaps to its position. Sharp and editorial; pairs with mono or condensed display weights.", + "target": "word", + "enter": { + "durationMs": 160, + "staggerMs": 60, + "easing": "steps(1, end)", + "from": { "opacity": 0, "y": 18 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 160, + "staggerMs": 30, + "easing": "steps(1, end)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": -18 } + }, + "swap": { "mode": "crossfade", "overlapMs": 0, "microDelayMs": 60 }, + "notes": "The steps easing makes this read as a series of discrete cuts, not motion. Don't expect smoothness — that's the point. Use sparingly; one or two beats per video at most." +} diff --git a/skills/hyperframes/assets/text-effects/shared-axis-z.json b/skills/hyperframes/assets/text-effects/shared-axis-z.json new file mode 100644 index 000000000..25dedf3cc --- /dev/null +++ b/skills/hyperframes/assets/text-effects/shared-axis-z.json @@ -0,0 +1,20 @@ +{ + "id": "shared-axis-z", + "name": "Shared Axis Z", + "description": "Scale-based depth transition along the Z axis. One context fades out small (recedes), the next fades in at full scale (arrives). Reads as a contextual shift — best for swapping between hierarchically related sections.", + "target": "element", + "enter": { + "durationMs": 360, + "easing": "cubic-bezier(0.2, 0, 0, 1)", + "from": { "opacity": 0, "scale": 1.08 }, + "to": { "opacity": 1, "scale": 1 } + }, + "exit": { + "durationMs": 360, + "easing": "cubic-bezier(0.4, 0, 1, 1)", + "from": { "opacity": 1, "scale": 1 }, + "to": { "opacity": 0, "scale": 0.92 } + }, + "swap": { "mode": "crossfade", "overlapMs": 180, "microDelayMs": 0 }, + "notes": "Outgoing scales DOWN (recedes), incoming scales DOWN from above (arrives from depth). The 0.92/1.08 deltas are intentionally subtle — pushing them beyond 0.85/1.15 makes the effect feel theatrical rather than spatial." +} diff --git a/skills/hyperframes/assets/text-effects/shimmer-sweep.json b/skills/hyperframes/assets/text-effects/shimmer-sweep.json new file mode 100644 index 000000000..5b42dc008 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/shimmer-sweep.json @@ -0,0 +1,20 @@ +{ + "id": "shimmer-sweep", + "name": "Shimmer Sweep", + "description": "A subtle horizontal light sweep glides across the element from left to right, leaving the element fully visible after the sweep passes. The text itself doesn't move — only the highlight band does. Reads as premium / luxury polish.", + "target": "element", + "enter": { + "durationMs": 680, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "backgroundPosition": "-100% 0%" }, + "to": { "opacity": 1, "backgroundPosition": "100% 0%" } + }, + "exit": { + "durationMs": 360, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1 }, + "to": { "opacity": 0 } + }, + "swap": { "mode": "crossfade", "overlapMs": 100, "microDelayMs": 0 }, + "notes": "Implementer: apply a linear-gradient background to the text (or its container) with `background-clip: text` so the shimmer reveals the text rather than a separate highlight bar. The shimmer band runs from -100% to 100% of background-position-x." +} diff --git a/skills/hyperframes/assets/text-effects/short-slide-down.json b/skills/hyperframes/assets/text-effects/short-slide-down.json new file mode 100644 index 000000000..380e8e60a --- /dev/null +++ b/skills/hyperframes/assets/text-effects/short-slide-down.json @@ -0,0 +1,23 @@ +{ + "id": "short-slide-down", + "name": "Short Slide Down", + "description": "Each word drops in from above and pushes the line stack downward — the line never moves; instead each new word arrives at the top of the stack and shoves what's already there. Useful for stacked vertical text where reveal order is meaningful.", + "target": "word", + "enter": { + "durationMs": 420, + "staggerMs": 160, + "easing": "cubic-bezier(0.18, 1, 0.32, 1)", + "from": { "opacity": 0, "y": -36 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 320, + "staggerMs": 30, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": 14 } + }, + "swap": { "mode": "crossfade", "overlapMs": 100, "microDelayMs": 0 }, + "layoutAware": true, + "notes": "Layout-aware: words must be stacked vertically (one per line), not horizontal. Implementer wraps each word in a block-display container so they push the next word down as they enter." +} diff --git a/skills/hyperframes/assets/text-effects/short-slide-right.json b/skills/hyperframes/assets/text-effects/short-slide-right.json new file mode 100644 index 000000000..357ad0ce2 --- /dev/null +++ b/skills/hyperframes/assets/text-effects/short-slide-right.json @@ -0,0 +1,27 @@ +{ + "id": "short-slide-right", + "name": "Short Slide Right", + "description": "The whole phrase glides in from the left as one shared move; individual words reveal only by opacity, not by sliding independently. The line settles to its centered position as words light up sequentially.", + "target": "word", + "enter": { + "durationMs": 560, + "staggerMs": 120, + "easing": "cubic-bezier(0.16, 1, 0.3, 1)", + "lineFrom": { "x": -48 }, + "lineTo": { "x": 0 }, + "from": { "opacity": 0 }, + "to": { "opacity": 1 } + }, + "exit": { + "durationMs": 360, + "staggerMs": 24, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "lineFrom": { "x": 0 }, + "lineTo": { "x": -16 }, + "from": { "opacity": 1 }, + "to": { "opacity": 0 } + }, + "swap": { "mode": "crossfade", "overlapMs": 120, "microDelayMs": 0 }, + "layoutAware": true, + "notes": "Layout-aware: animate the line container's x-position separately from each word's opacity. Words DO NOT slide individually — they only fade. Two GSAP tweens compose this effect: one on the line wrapper, one staggered across the word spans." +} diff --git a/skills/hyperframes/assets/text-effects/soft-blur-in.json b/skills/hyperframes/assets/text-effects/soft-blur-in.json new file mode 100644 index 000000000..372bfc16e --- /dev/null +++ b/skills/hyperframes/assets/text-effects/soft-blur-in.json @@ -0,0 +1,22 @@ +{ + "id": "soft-blur-in", + "name": "Soft Blur In", + "description": "Each character fades in with a gentle upward drift and a brief blur trail. Premium and atmospheric — best for hero headlines where the focal text should feel like it materializes rather than appears.", + "target": "char", + "enter": { + "durationMs": 700, + "staggerMs": 20, + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "y": 14, "filter": "blur(8px)" }, + "to": { "opacity": 1, "y": 0, "filter": "blur(0px)" } + }, + "exit": { + "durationMs": 380, + "staggerMs": 8, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0, "filter": "blur(0px)" }, + "to": { "opacity": 0, "y": -6, "filter": "blur(4px)" } + }, + "swap": { "mode": "crossfade", "overlapMs": 120, "microDelayMs": 0 }, + "notes": "Pair with serif or display weights — the blur softens the silhouette and reads as polished. Avoid with mono fonts; the blur halo fights with the rigid grid." +} diff --git a/skills/hyperframes/assets/text-effects/spring-scale-in.json b/skills/hyperframes/assets/text-effects/spring-scale-in.json new file mode 100644 index 000000000..cca6a50de --- /dev/null +++ b/skills/hyperframes/assets/text-effects/spring-scale-in.json @@ -0,0 +1,22 @@ +{ + "id": "spring-scale-in", + "name": "Spring Scale In", + "description": "Words pop in with a spring overshoot — scale runs 0.4 → 1.06 → 1.0. Physical and slightly playful; the overshoot reads as bouncy without crossing into cartoonish.", + "target": "word", + "enter": { + "durationMs": 280, + "staggerMs": 80, + "easing": "cubic-bezier(0.34, 1.56, 0.64, 1)", + "from": { "opacity": 0, "scale": 0.4 }, + "to": { "opacity": 1, "scale": 1 } + }, + "exit": { + "durationMs": 240, + "staggerMs": 20, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "scale": 1 }, + "to": { "opacity": 0, "scale": 0.8 } + }, + "swap": { "mode": "crossfade", "overlapMs": 80, "microDelayMs": 0 }, + "notes": "The spring easing's overshoot (1.56) lands at ~6% past the target before settling. Pair with playful brands or product launches — avoid for clinical / enterprise contexts." +} diff --git a/skills/hyperframes/assets/text-effects/stagger-from-center.json b/skills/hyperframes/assets/text-effects/stagger-from-center.json new file mode 100644 index 000000000..12ddfc72a --- /dev/null +++ b/skills/hyperframes/assets/text-effects/stagger-from-center.json @@ -0,0 +1,24 @@ +{ + "id": "stagger-from-center", + "name": "Stagger From Center", + "description": "Characters reveal outward from the center of the headline — middle glyph appears first, edges last. Emphasizes the keyword's core; useful for short slogans where the central word carries the punch.", + "target": "char", + "enter": { + "durationMs": 480, + "staggerMs": 32, + "staggerOrder": "center-out", + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "y": 12, "scale": 0.92 }, + "to": { "opacity": 1, "y": 0, "scale": 1 } + }, + "exit": { + "durationMs": 340, + "staggerMs": 14, + "staggerOrder": "center-out", + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0, "scale": 1 }, + "to": { "opacity": 0, "y": -8, "scale": 0.94 } + }, + "swap": { "mode": "crossfade", "overlapMs": 100, "microDelayMs": 0 }, + "notes": "Implementer-only: stagger order is center-out, NOT DOM order. Compute each character's rank as `|index - centerIndex|`, ties resolved by lower index first." +} diff --git a/skills/hyperframes/assets/text-effects/stagger-from-edges.json b/skills/hyperframes/assets/text-effects/stagger-from-edges.json new file mode 100644 index 000000000..6e3b1a70b --- /dev/null +++ b/skills/hyperframes/assets/text-effects/stagger-from-edges.json @@ -0,0 +1,24 @@ +{ + "id": "stagger-from-edges", + "name": "Stagger From Edges", + "description": "Mirror of stagger-from-center — edge characters appear first, center last. The headline assembles inward toward its keyword core; emphasizes that the middle word is the destination.", + "target": "char", + "enter": { + "durationMs": 480, + "staggerMs": 32, + "staggerOrder": "edges-in", + "easing": "cubic-bezier(0.22, 1, 0.36, 1)", + "from": { "opacity": 0, "y": 12, "scale": 0.92 }, + "to": { "opacity": 1, "y": 0, "scale": 1 } + }, + "exit": { + "durationMs": 340, + "staggerMs": 14, + "staggerOrder": "edges-in", + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0, "scale": 1 }, + "to": { "opacity": 0, "y": -8, "scale": 0.94 } + }, + "swap": { "mode": "crossfade", "overlapMs": 100, "microDelayMs": 0 }, + "notes": "Implementer-only: stagger order is edges-in. Compute each character's rank as `(text.length - 1) / 2 - |index - centerIndex|`, ties resolved by higher index first." +} diff --git a/skills/hyperframes/assets/text-effects/top-down-letters.json b/skills/hyperframes/assets/text-effects/top-down-letters.json new file mode 100644 index 000000000..4b23e770f --- /dev/null +++ b/skills/hyperframes/assets/text-effects/top-down-letters.json @@ -0,0 +1,22 @@ +{ + "id": "top-down-letters", + "name": "Top-Down Letters", + "description": "Mirror of bottom-up-letters — characters descend from above the baseline rather than rising from below. Use when the layout wants downward energy or when the headline is positioned below another anchored element.", + "target": "char", + "enter": { + "durationMs": 320, + "staggerMs": 65, + "easing": "cubic-bezier(0.18, 1, 0.32, 1)", + "from": { "opacity": 0, "y": -56 }, + "to": { "opacity": 1, "y": 0 } + }, + "exit": { + "durationMs": 280, + "staggerMs": 14, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": 22 } + }, + "swap": { "mode": "crossfade", "overlapMs": 80, "microDelayMs": 0 }, + "notes": "Choose between bottom-up and top-down based on layout pull, not by default. A headline anchored to the bottom of frame benefits from top-down energy; one anchored at top benefits from bottom-up." +} diff --git a/skills/hyperframes/assets/text-effects/typewriter.json b/skills/hyperframes/assets/text-effects/typewriter.json new file mode 100644 index 000000000..4c333557a --- /dev/null +++ b/skills/hyperframes/assets/text-effects/typewriter.json @@ -0,0 +1,22 @@ +{ + "id": "typewriter", + "name": "Typewriter", + "description": "Per-character stepped reveal that types each glyph in sequence. No interpolation — characters pop into existence at discrete intervals. Reads as mechanical, editorial, or terminal-style.", + "target": "char", + "enter": { + "durationMs": 200, + "staggerMs": 50, + "easing": "steps(1, end)", + "from": { "opacity": 0 }, + "to": { "opacity": 1 } + }, + "exit": { + "durationMs": 240, + "staggerMs": 12, + "easing": "cubic-bezier(0.7, 0, 0.84, 0)", + "from": { "opacity": 1, "y": 0 }, + "to": { "opacity": 0, "y": -4 } + }, + "swap": { "mode": "crossfade", "overlapMs": 0, "microDelayMs": 80 }, + "notes": "Pair with a blinking caret element if you want the full terminal feel. The 50ms stagger lands around 1200 chars/min — fast but legible. Increase to 80-100ms if you want a deliberate typing pace." +} diff --git a/skills/hyperframes/references/beat-direction.md b/skills/hyperframes/references/beat-direction.md index 04b0e12d1..5be413817 100644 --- a/skills/hyperframes/references/beat-direction.md +++ b/skills/hyperframes/references/beat-direction.md @@ -15,6 +15,10 @@ The first describes pixels. The second describes an experience. Write the second Each beat should have: +**For text animations:** pick a named effect from [`text-effects.md`](text-effects.md) and name it by ID in the storyboard. Don't describe "text fades in" — write `soft-blur-in` or `kinetic-center-build`. The sub-agent reads the effect JSON and implements it exactly. + +--- + ### Concept The big idea for this beat in 2-3 sentences. What visual WORLD are we in? What metaphor drives it? What should the viewer FEEL? This is the most important part — everything else flows from it. @@ -29,15 +33,17 @@ Cultural and design references, not hex codes: ### Animation choreography -Specific motion verbs per element — not "it animates in" but HOW: +Specific motion verbs per element — not "it animates in" but HOW. Verbs come from the beat's concept and content, not from an energy bucket. A wellness brand's "slow" beat might still have something that DROPS if the content is about letting go. A stats beat might FLOAT if the brand's identity is weightless. -| Energy | Verbs | Example | -| ------------- | --------------------------------------------- | ------------------------------------- | -| High impact | SLAMS, CRASHES, PUNCHES, STAMPS, SHATTERS | "$1.9T" SLAMS in from left at -5° | -| Medium energy | CASCADE, SLIDES, DROPS, FILLS, DRAWS | Three cards CASCADE in staggered 0.3s | -| Low energy | types on, FLOATS, morphs, COUNTS UP, fades in | Counter COUNTS UP from 0 to 135K | +The vocabulary of motion verbs (organized by physical character, not by energy level): -Every element gets a verb. If you can't name the verb, the element is not yet designed. +**Impact / weight:** SLAMS, CRASHES, PUNCHES, STAMPS, SHATTERS, DROPS (with force) +**Directional / deliberate:** SLIDES, PUSHES, PULLS, WIPES, CUTS +**Reveals / builds:** DRAWS, FILLS, GROWS, EXPANDS, ASSEMBLES, COUNTS UP +**Organic / ambient:** FLOATS, DRIFTS, BREATHES, PULSES, ORBITS, MORPHS +**Mechanical / precise:** TYPES ON, CLICKS, LOCKS IN, SNAPS, STEPS + +Every element gets a verb. If you can't name the verb, the element is not yet designed. The verb should follow from the beat's concept — not from a lookup of what "high energy" or "low energy" beats use. ### Transition @@ -51,23 +57,82 @@ How this beat hands off to the next. Specify the type and parameters. | Any moment the music/VO punctuates with a downbeat or SFX hit | Beats that ease from one composition into the next with shared motion vocabulary | Sequences of 3+ quick tempo-matched switches | | Brand moments where the transition itself _is_ the visual | Minimal/editorial pacing | Anytime a 0.3-0.8s transition would feel too slow | -Rule of thumb: if the beat is the _centerpiece_ of the video, shader-transition into it. If the beat is connective tissue, CSS-transition. A brand reel of 5-7 beats usually wants 1-2 shader transitions (the hero reveal + the CTA) and the rest CSS or hard cuts — too many shader transitions flatten their impact. - -**CSS transitions** (choose from `skills/hyperframes/references/transitions/catalog.md`): - -- Velocity-matched upward: exit `y:-150, blur:30px, 0.33s power2.in` → entry `y:150→0, blur:30px→0, 1.0s power2.out` -- Whip pan: exit `x:-400, blur:24px, 0.3s power3.in` → entry `x:400→0, blur:24px→0, 0.3s power3.out` -- Blur through: exit `blur:20px, 0.3s` → entry `blur:20px→0, 0.25s power3.out` -- Zoom through: exit `scale:1→1.2, blur:20px, 0.2s power3.in` → entry `scale:0.75→1, blur:20px→0, 0.5s expo.out` -- Hard cut / smash cut (for rapid-fire sequences) - -**Shader transitions** (choose from `packages/shader-transitions/README.md`): - -- Cross-Warp Morph (organic, versatile) — 0.5-0.8s, power2.inOut -- Cinematic Zoom (professional momentum) — 0.4-0.6s, power2.inOut -- Gravitational Lens (otherworldly) — 0.6-1.0s, power2.inOut -- Glitch (aggressive, high energy) — 0.3-0.5s -- See `packages/shader-transitions/README.md` for the full API, available shaders, and setup +Rule of thumb: if the beat is the _centerpiece_ of the video, shader-transition into it. If the beat is connective tissue, a CSS crossfade is fine. A brand reel of 5-7 beats usually wants 1-2 shader transitions (the hero reveal + the CTA) — too many flatten their impact. + +**Mixing shader and CSS crossfade transitions in one composition is supported.** Omit `shader` on any transition entry to get a smooth opacity crossfade. HyperShader manages all scene visibility regardless: + +```js +var tl = HyperShader.init({ + bgColor: "#0a0a0f", + scenes: ["s1", "s2", "s3", "s4"], + transitions: [ + { time: 4.0, shader: "sdf-iris", duration: 0.7 }, // WebGL shader + { time: 8.5, duration: 0.8 }, // no shader → CSS crossfade + { time: 13.0, shader: "domain-warp", duration: 0.6 }, + ], +}); +// Add beat animations to the returned tl AFTER init() +tl.fromTo("#hero", { opacity: 0 }, { opacity: 1, duration: 0.6 }, 0.2); +window.__timelines["main"] = tl; +``` + +Let HyperShader create the timeline — don't pass a pre-built `timeline:` option. Add all composition tweens to the returned `tl` after the call. + +**CSS transitions** — 30+ patterns across 13 categories. Full code in `skills/hyperframes/references/transitions/`. Pick based on the energy and feel: + +| Category | Patterns | Motion character | +| ------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------- | +| **Push / slide** | Push slide, vertical push, elastic push, squeeze | Content moves through the frame as if on a continuous surface | +| **Scale / zoom** | Zoom through, zoom out | Perspective shifts — moving toward or away from content | +| **Radial / clip** | Circle iris, diamond iris, diagonal split | Geometric reveal — content emerges or is covered by a shape | +| **3D** | 3D card flip | Physical — content flips like a tangible object | +| **Dissolve** | Crossfade, blur crossfade, focus pull, color dip | Overlap and blend — both scenes exist simultaneously during the transition | +| **Cover / blinds** | Staggered color blocks, horizontal blinds (6/12 strips), vertical blinds | Structural — content is sliced, layered, or covered | +| **Light** | Light leak overlays, overexposure burn, film burn | Organic film — light bleeds across the frame | +| **Distortion** | Glitch (CSS RGB jitter), chromatic aberration, ripple, VHS tape | Instability — the image itself appears to malfunction | +| **Blur** | Blur through, directional blur | Soft defocus — content blurs in or out | +| **Mechanical** | Shutter (two-half), clock wipe (9-point wedge) | Precision — transitions with visible mechanical logic | +| **Grid** | Grid dissolve (12/120 cells) | Fragmentation — the frame breaks into pieces | +| **Destruction** | Page burn (SVG clip-path + canvas rim) | Dramatic decay — the previous scene is destroyed | +| **Other** | Gravity drop, morph circle | Physical or shape-based motion that doesn't fit other categories | + +Common quick-picks: + +- **Velocity-matched upward**: exit `y:-150, blur:30px, 0.33s power2.in` → entry `y:150→0, blur:30px→0, 1.0s power2.out` +- **Whip pan**: exit `x:-400, blur:24px, 0.3s power3.in` → entry `x:400→0, blur:24px→0, 0.3s power3.out` +- **Blur through**: exit `blur:20px, 0.3s` → entry `blur:20px→0, 0.25s power3.out` +- **Zoom through**: exit `scale:1→1.2, blur:20px, 0.2s power3.in` → entry `scale:0.75→1, blur:20px→0, 0.5s expo.out` +- **Hard cut / smash cut**: instant, for rapid-fire sequences + +Timing presets: snappy (0.2s), smooth (0.4s), gentle (0.6s), dramatic (0.5s), instant (0.15s), luxe (0.7s). + +**Shader transitions** — 14 built-in WebGL GPU effects. Install with `npx hyperframes add `. Full API in shader-transitions docs. + +| Shader | Visual description | Duration range | +| ----------------------- | ---------------------------------------------------------------------------------------------- | -------------- | +| **domain-warp** | Organic FBM dissolve — both scenes warp toward each other with an accent flash at the midpoint | 0.5–0.8s | +| **ridged-burn** | Multifractal mask reveals the incoming scene through a burn ramp with sparks at the edge | 0.5–0.8s | +| **whip-pan** | 10-sample horizontal motion blur + lateral crossfade — reads like a camera pan between shots | 0.3–0.5s | +| **sdf-iris** | Circle SDF expands from center, with accent-tinted glow rings at the expanding edge | 0.5–0.7s | +| **ripple-waves** | Radial standing-wave UV displacement — content ripples outward as scenes cross | 0.6–1.0s | +| **gravitational-lens** | Pinch pull toward center + R/B chromatic separation — content bends inward then releases | 0.6–1.0s | +| **cinematic-zoom** | 12 RGB-offset radial zoom blur samples — motion streak radiating from center | 0.4–0.6s | +| **chromatic-split** | R/B radial channel shift outward, G fixed — channels separate then rejoin | 0.3–0.5s | +| **swirl-vortex** | CCW swirl with FBM noise — content spirals away and the new scene spirals in | 0.5–0.8s | +| **thermal-distortion** | Vertical sine + FBM horizontal displacement — heat-haze shimmer across the frame | 0.5–0.8s | +| **flash-through-white** | Fade through white midpoint — almost invisible at 0.01s, noticeable at 0.3s | 0.01s–0.3s | +| **cross-warp-morph** | FBM vector field displaces both scenes; a third FBM biases the wipe direction | 0.5–0.8s | +| **light-leak** | Fixed off-frame light source with exponential falloff, warmth, and a ridge flare | 0.5–0.8s | +| **glitch** | Line displacement + RGB lateral split + scan modulation + posterization + flicker | 0.3–0.5s | + +**You are not limited to what's listed here.** These are the built-in options, but you can and should: + +- **Write custom GLSL shaders** from scratch for unique transition effects +- **Search online** for shader code (ShaderToy, GLSL Sandbox, GitHub) and adapt it +- **Build custom CSS transitions** that aren't in any category — combine clip-path, transforms, filters in new ways +- **Ask the user** to provide or find specific effects if you need something specialized + +If the storyboard calls for an effect that doesn't exist yet — build it. The framework renders anything a browser can run. ### Depth layers @@ -88,12 +153,16 @@ What sounds at what moment: Before writing HTML, declare your scene rhythm: which scenes are quick hits, which are holds, where do shaders land, where does energy peak. Name the pattern — fast-fast-SLOW-fast-SHADER-hold — before implementing. -| Video type | Typical rhythm pattern | -| ---------------------- | --------------------------------- | -| Social ad (15s) | hook-PUNCH-hold-CTA | -| Product demo (30-60s) | slow-build-BUILD-PEAK-breathe-CTA | -| Launch teaser (10-20s) | SLAM-proof-SLAM-hold | -| Brand reel (20-45s) | drift-build-PEAK-drift-resolve | +**Derive the rhythm from the storyboard and the brand, not from a lookup.** A 15-second social ad for an architectural firm and a 15-second social ad for a gaming brand have different rhythms — both are 15 seconds, but one is slow-reveal-hold-CTA and the other is rapid-fire-SLAM-hook. Video type sets constraints (duration, approximate beat count); the brand and content determine whether those beats are slow or fast, sparse or dense, dramatic or controlled. + +Questions that drive rhythm decisions: + +- What emotional journey should the viewer take? Where is the peak moment? +- Where does the narration land its heaviest emphasis? That's usually where energy should peak. +- What does the brand's own visual pacing suggest — unhurried or urgent? +- How many beats can the duration actually support without feeling rushed or padded? + +A social ad that tries to hook in 2s, showcase 3 features, and end with a CTA in 15s will feel like noise. Sometimes "hook-hold-CTA" with one strong feature is the right rhythm for 15 seconds. Name the rhythm you've planned before implementing. --- diff --git a/skills/hyperframes/references/dynamic-techniques.md b/skills/hyperframes/references/dynamic-techniques.md index c0cab609f..af271e39a 100644 --- a/skills/hyperframes/references/dynamic-techniques.md +++ b/skills/hyperframes/references/dynamic-techniques.md @@ -4,13 +4,25 @@ You are here because SKILL.md told you to read this file before writing animatio ## Technique Selection by Energy -| Energy level | Highlight | Exit | Cycle pattern | -| ------------ | ------------------------------------- | ------------------- | ----------------------------------------- | -| High | Karaoke with accent glow + scale pop | Scatter or drop | Alternate highlight styles every 2 groups | -| Medium-high | Karaoke with color pop | Scatter or collapse | Alternate every 3 groups | -| Medium | Karaoke (subtle, white only) | Fade + slide | Alternate every 3 groups | -| Medium-low | Karaoke (minimal scale change) | Fade | Single style, vary ease per group | -| Low | Karaoke (warm tones, slow transition) | Collapse | Alternate every 4 groups | +Captions are a constrained surface — the highlight and exit technique is closely tied to how much intensity the spoken content carries. The table below is a calibration reference. If DESIGN.md or the storyboard specifies a caption style, that overrides anything here. + +The core principle: **all energy levels use karaoke highlight as the baseline.** The difference is intensity — not the technique type. + +**What changes with energy:** + +- **Highlight intensity:** high energy gets accent color + glow + 15% scale pop on active words. Low energy gets a gentle white shift with 3% scale. The karaoke behavior is the same; the amplitude is different. +- **Exit style:** high energy exits scatter or drop (the word leaves with motion). Low energy exits collapse (the word simply fades or shrinks). The exit should express the same energy as the content. +- **Cycle variation:** high energy alternates highlight styles every 2 groups for variety. Low energy uses a single consistent style, varying only the ease. Variation itself creates energy; consistency creates calm. + +Calibration reference (starting points, not rules): + +| Energy level | Highlight amplitude | Exit | Cycle variation | +| ------------ | ----------------------------------- | ------------------- | --------------- | +| High | Accent color + glow + 15% scale pop | Scatter or drop | Every 2 groups | +| Medium-high | Color pop, no glow | Scatter or collapse | Every 3 groups | +| Medium | White shift only | Fade + slide | Every 3 groups | +| Medium-low | Minimal scale change | Fade | Single style | +| Low | Warm tones, slow transition | Collapse | Single style | **All energy levels use karaoke highlight as the baseline.** The difference is intensity — high energy gets accent color + glow + 15% scale pop on active words, low energy gets a gentle white shift with 3% scale. diff --git a/skills/hyperframes/references/html-in-canvas-patterns.md b/skills/hyperframes/references/html-in-canvas-patterns.md new file mode 100644 index 000000000..f826b1f92 --- /dev/null +++ b/skills/hyperframes/references/html-in-canvas-patterns.md @@ -0,0 +1,507 @@ +# HTML-in-Canvas Patterns + +HyperFrames' most powerful visual capability. Capture ANY live HTML/CSS as a GPU texture, then render it through WebGL shaders, Three.js 3D scenes, or post-processing effects — at 60fps, pixel-perfect, with every CSS feature supported. + +**Read this file when a beat deserves cinematic treatment beyond flat GSAP animations.** Use for 1-3 hero beats per video, not every beat. The rest can use standard GSAP — the contrast between flat beats and HTML-in-Canvas beats IS part of the visual storytelling. + +--- + +## Core Boilerplate (same in every HTML-in-Canvas composition) + +Every HTML-in-Canvas effect shares this structure. Learn this once, adapt it for any effect. + +```html + + +
+ +
+
+ + + +``` + +```js +// 3. Feature detection — always check, always provide fallback +function isHiCSupported() { + var tc = document.createElement("canvas"); + if (!("layoutSubtree" in tc)) return false; + tc.setAttribute("layoutsubtree", ""); + var ctx = tc.getContext("2d"); + return ctx && typeof ctx.drawElementImage === "function"; +} +var apiOk = isHiCSupported(); + +// 4. Capture function — call this every frame in onUpdate +var capCanvas = document.getElementById("hic-source"); +var capCtx = capCanvas.getContext("2d"); +function captureContent() { + if (apiOk) { + capCtx.drawElementImage(document.getElementById("hic-content"), 0, 0, 1920, 1080); + } +} + +// 5. Drive from GSAP timeline — capture + render every frame +tl.to( + proxy, + { + /* your animation properties */ + duration: BEAT_DURATION, + ease: "sine.inOut", + onUpdate: function () { + captureContent(); + // render your effect here (Three.js or WebGL2) + }, + }, + 0, +); +``` + +**Fallback:** When `drawElementImage` is not available (preview without Chrome flag), draw a solid-color placeholder or use Canvas 2D text. The HyperFrames renderer auto-enables the flag — the effect WILL work in the final video. See the liquid-glass block for a complete fallback example. + +--- + +## Effect Catalog + +### 1. 3D Rotation with Bloom (Three.js) + +**What it looks like:** Content floats in 3D space, slowly rotating with cinematic glow around bright edges. Like a product screenshot displayed in a dark theater. + +**When to use:** Hero product showcase, feature reveal, CTA with premium feel. + +**Key Three.js components:** `PlaneGeometry` + `CanvasTexture` + `EffectComposer` + `UnrealBloomPass` + +```js +// After the boilerplate above, add: +var scene3d = new THREE.Scene(); +var camera = new THREE.PerspectiveCamera(45, 1920 / 1080, 0.1, 100); +camera.position.set(0, 0, 4); + +var renderer = new THREE.WebGLRenderer({ + canvas: document.getElementById("hic-output"), + antialias: true, + alpha: true, +}); +renderer.setSize(1920, 1080); + +var texture = new THREE.CanvasTexture(capCanvas); +var mesh = new THREE.Mesh( + new THREE.PlaneGeometry(3.6, 2.2), + new THREE.MeshBasicMaterial({ map: texture }), +); +scene3d.add(mesh); + +// Post-processing: bloom for cinematic glow. +// EffectComposer / RenderPass / UnrealBloomPass are ES-module named imports +// (see the import block below) — they're NOT properties of THREE in modern +// versions. Three.js r150+ removed the UMD `examples/js/` globals. +var composer = new EffectComposer(renderer); +composer.addPass(new RenderPass(scene3d, camera)); +composer.addPass(new UnrealBloomPass(new THREE.Vector2(1920, 1080), 0.3, 0.4, 0.85)); + +var proxy = { rotY: -0.12, zoom: 4.2 }; +tl.to( + proxy, + { + rotY: 0.12, + zoom: 3.6, + duration: BEAT_DURATION, + ease: "sine.inOut", + onUpdate: function () { + captureContent(); + texture.needsUpdate = true; + mesh.rotation.y = proxy.rotY; + camera.position.z = proxy.zoom; + composer.render(); + }, + }, + 0, +); +``` + +**Load Three.js and post-processing via ESM (use a `type="module"` script):** + +```html + +``` + +The `examples/js/` path was removed in Three.js r152. Use `examples/jsm/` (ES modules) with `three@0.181.2` — the version used by the HyperFrames Three.js adapter. + +--- + +### 2. Magnetic Cursor Distortion (Raw WebGL2) + +**What it looks like:** Content warps and bends toward a moving point, like a magnet pulling on pixels. Chromatic aberration splits RGB channels at the distortion site. + +**When to use:** Interactive feel, product demo with cursor, "look at THIS feature" moment. + +**Key technique:** Custom fragment shader with Gaussian warp + chromatic split. No Three.js needed — just raw WebGL2. + +```js +// WebGL2 setup +var gl = document.getElementById("hic-output").getContext("webgl2", { + alpha: false, + preserveDrawingBuffer: true, +}); + +// Vertex shader — full-screen quad +var VS = `#version 300 es +in vec2 a_pos; +out vec2 v_uv; +void main() { + v_uv = a_pos * 0.5 + 0.5; + gl_Position = vec4(a_pos, 0.0, 1.0); +}`; + +// Fragment shader — magnetic warp + chromatic aberration +var FS = `#version 300 es +precision highp float; +in vec2 v_uv; +out vec4 fragColor; +uniform sampler2D u_tex; +uniform vec2 u_cursor; // cursor position (0-1) +uniform float u_strength; // warp strength (0-1) + +void main() { + vec2 uv = v_uv; + vec2 delta = uv - u_cursor; + float dist = length(delta); + float warp = u_strength * exp(-dist * dist * 8.0); + vec2 warped = uv - delta * warp * 0.3; + + // Chromatic aberration at distortion site + float aberration = warp * 0.008; + float r = texture(u_tex, warped + vec2(aberration, 0.0)).r; + float g = texture(u_tex, warped).g; + float b = texture(u_tex, warped - vec2(aberration, 0.0)).b; + fragColor = vec4(r, g, b, 1.0); +}`; + +// Compile, link, setup quad geometry, upload texture... +// (See registry/blocks/vfx-magnetic/vfx-magnetic.html for complete implementation) + +// Drive cursor position from GSAP +var proxy = { cx: 0.2, cy: 0.5, strength: 0.0 }; +tl.to( + proxy, + { + cx: 0.8, + cy: 0.4, + strength: 1.0, + duration: BEAT_DURATION, + ease: "power2.inOut", + onUpdate: function () { + captureContent(); + // Upload texture, set uniforms, draw + gl.uniform2f(cursorLoc, proxy.cx, proxy.cy); + gl.uniform1f(strengthLoc, proxy.strength); + gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + }, + }, + 0, +); +``` + +--- + +### 3. Shatter / Fragment Explosion (Three.js) + +**What it looks like:** Content breaks into geometric fragments that fly apart, revealing what's behind. + +**When to use:** Dramatic transition, "breaking free" moment, tension release. + +**Key technique:** Subdivide the source texture into triangle mesh fragments using BufferGeometry, then animate each fragment's position/rotation with GSAP. + +Study `registry/blocks/vfx-shatter/vfx-shatter.html` for the complete 1156-line implementation. The core idea: + +```js +// 1. Capture content to texture (same boilerplate) +// Seeded PRNG for determinism — Math.random() is banned +function mulberry32(seed) { + return function () { + seed |= 0; + seed = (seed + 0x6d2b79f5) | 0; + var t = Math.imul(seed ^ (seed >>> 15), 1 | seed); + t ^= t + Math.imul(t ^ (t >>> 7), 61 | t); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} +var rng = mulberry32(42); + +// 2. Create N triangle fragments from the texture +var fragments = []; +for (var i = 0; i < NUM_FRAGMENTS; i++) { + var geom = new THREE.BufferGeometry(); + var mesh = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({ map: texture })); + scene3d.add(mesh); + fragments.push({ mesh: mesh, targetPos: randomExplosionVector(rng), delay: rng() * 0.5 }); +} + +// 3. Animate: first hold still, then EXPLODE +tl.to({}, { duration: holdTime }, 0); +fragments.forEach(function (frag) { + tl.to( + frag.mesh.position, + { + x: frag.targetPos.x, + y: frag.targetPos.y, + z: frag.targetPos.z, + duration: 0.8, + ease: "power3.in", + }, + holdTime + frag.delay, + ); + tl.to( + frag.mesh.rotation, + { x: rng() * 4, y: rng() * 4, duration: 0.8, ease: "power2.in" }, + holdTime + frag.delay, + ); +}); +``` + +--- + +### 4. Liquid / Fluid Surface (Three.js) + +**What it looks like:** Content floats above a rippling liquid surface with real-time wave dynamics. Or content IS the surface, undulating like water. + +**When to use:** Organic/premium feel, ambient background, "living" product showcase. + +**Key technique:** Subdivided PlaneGeometry with vertex displacement driven by noise functions in a vertex shader. + +Study `registry/blocks/vfx-liquid-background/vfx-liquid-background.html` for the 1244-line implementation. Core idea: + +```js +// Custom vertex shader with wave displacement +var vertexShader = ` + varying vec2 vUv; + uniform float u_time; + void main() { + vUv = uv; + vec3 pos = position; + // Sine wave displacement + pos.z += sin(pos.x * 3.0 + u_time * 2.0) * 0.15; + pos.z += cos(pos.y * 2.5 + u_time * 1.5) * 0.1; + gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); + } +`; + +var mesh = new THREE.Mesh( + new THREE.PlaneGeometry(4, 3, 64, 64), // heavily subdivided for smooth waves + new THREE.ShaderMaterial({ + vertexShader: vertexShader, + fragmentShader: `varying vec2 vUv; uniform sampler2D u_tex; + void main() { gl_FragColor = texture2D(u_tex, vUv); }`, + uniforms: { + u_tex: { value: texture }, + u_time: { value: 0 }, + }, + }), +); +``` + +--- + +### 5. Portal / Dimensional Reveal (Three.js) + +**What it looks like:** A glowing circular portal opens and content emerges through it from another dimension. + +**When to use:** Product reveal, "entering the app" moment, hero feature introduction. + +Study `registry/blocks/vfx-portal/vfx-portal.html` for the complete 863-line implementation. + +--- + +## When to Use HTML-in-Canvas vs Standard GSAP + +| Scenario | Use | Why | +| -------------------------------- | ------------------------------------ | ------------------------------------ | +| Hero product screenshot showcase | HTML-in-Canvas (3D rotation + bloom) | Makes flat UI feel cinematic | +| Feature list / stats | Standard GSAP | Content-focused, doesn't need 3D | +| CTA / brand reveal | HTML-in-Canvas (portal or magnetic) | Makes the moment memorable | +| Social proof / logos | Standard GSAP | Orderly cascade, trust is steady | +| Transition between acts | HTML-in-Canvas (shatter) | Dramatic act break | +| Background atmosphere | HTML-in-Canvas (liquid surface) | Premium ambient feel | +| Quick feature cards | Standard GSAP | Speed matters, 3D would slow it down | + +--- + +## More Effects You Can Build + +These aren't in the VFX blocks — build them yourself from the core boilerplate + a custom fragment shader. Each effect is a single GLSL function applied to the captured texture. + +### 6. Noise Dissolve + +Content dissolves into noise particles, revealing what's behind. Great for transitions. + +```glsl +// Fragment shader — noise-based dissolve +uniform float u_progress; // 0.0 = fully visible, 1.0 = fully dissolved +uniform sampler2D u_tex; + +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); +} + +void main() { + vec2 uv = v_uv; + float noise = hash(uv * 50.0); + float threshold = u_progress; + if (noise < threshold) { + // Edge glow at the dissolve boundary + float edge = smoothstep(threshold - 0.05, threshold, noise); + fragColor = vec4(1.0, 0.6, 0.2, 1.0) * (1.0 - edge); // orange edge glow + } else { + fragColor = texture(u_tex, uv); + } +} +``` + +### 7. Holographic / Iridescent + +Content gets a rainbow-shifting holographic sheen that moves with time. Premium, futuristic feel. + +```glsl +uniform float u_time; +uniform sampler2D u_tex; + +void main() { + vec4 color = texture(u_tex, v_uv); + // Iridescent color shift based on position + time + float angle = v_uv.x * 6.28 + v_uv.y * 3.14 + u_time * 0.5; + vec3 holo = vec3( + sin(angle) * 0.5 + 0.5, + sin(angle + 2.094) * 0.5 + 0.5, + sin(angle + 4.189) * 0.5 + 0.5 + ); + // Blend holographic over content (subtle overlay) + fragColor = vec4(mix(color.rgb, holo, 0.15 + 0.1 * sin(u_time)), color.a); +} +``` + +### 8. Scan Lines + CRT + +Retro CRT monitor look — scan lines, slight curvature, phosphor glow. Great for "code" or "terminal" beats. + +```glsl +uniform sampler2D u_tex; +uniform float u_time; + +void main() { + vec2 uv = v_uv; + // Barrel distortion (CRT curvature) + vec2 centered = uv - 0.5; + float dist = dot(centered, centered); + uv = uv + centered * dist * 0.15; + + vec4 color = texture(u_tex, uv); + // Scan lines + float scanline = sin(uv.y * 800.0) * 0.04; + color.rgb -= scanline; + // Slight RGB offset (phosphor) + color.r = texture(u_tex, uv + vec2(0.001, 0.0)).r; + color.b = texture(u_tex, uv - vec2(0.001, 0.0)).b; + // Vignette + float vignette = 1.0 - dist * 2.0; + fragColor = vec4(color.rgb * vignette, 1.0); +} +``` + +### 9. Frosted Glass Blur + +Content behind frosted glass — visible but softened, with subtle light refraction. Good for "behind the scenes" or "coming soon" moments. + +```glsl +uniform sampler2D u_tex; +uniform float u_blur; // 0.0 = clear, 1.0 = full frost + +void main() { + vec2 uv = v_uv; + vec4 color = vec4(0.0); + // Box blur with offset + float radius = u_blur * 0.015; + for (float x = -2.0; x <= 2.0; x += 1.0) { + for (float y = -2.0; y <= 2.0; y += 1.0) { + color += texture(u_tex, uv + vec2(x, y) * radius); + } + } + color /= 25.0; + // Add frost noise texture + float frost = fract(sin(dot(uv * 200.0, vec2(12.9898, 78.233))) * 43758.5453); + color.rgb += frost * 0.03 * u_blur; + fragColor = color; +} +``` + +### 10. Pixel Sort / Glitch Art + +Pixels rearrange themselves in vertical or horizontal strips — digital art aesthetic. Great for tech/creative brands. + +```glsl +uniform sampler2D u_tex; +uniform float u_intensity; // 0-1 + +void main() { + vec2 uv = v_uv; + // Random horizontal displacement per row + float row = floor(uv.y * 80.0); + float noise = fract(sin(row * 127.1) * 43758.5); + float displace = step(0.7, noise) * u_intensity * 0.1; + // Shift UV with RGB split + float r = texture(u_tex, uv + vec2(displace, 0.0)).r; + float g = texture(u_tex, uv).g; + float b = texture(u_tex, uv - vec2(displace * 0.5, 0.0)).b; + fragColor = vec4(r, g, b, 1.0); +} +``` + +--- + +## Creating ANY Custom Effect + +The fragment shaders above are templates. The pattern is always: + +1. **Capture your HTML content** with `drawElementImage` (the boilerplate at the top) +2. **Upload the captured canvas as a WebGL texture** +3. **Write a fragment shader** that reads from the texture and outputs modified colors +4. **Drive shader uniforms from GSAP** via `onUpdate` + +Any GLSL effect from ShaderToy, The Book of Shaders, CodePen, or anywhere else can be adapted: + +1. Find an effect you like (search "GLSL [effect name]" or browse shadertoy.com) +2. Copy the fragment shader +3. Replace `iResolution` with `vec2(1920.0, 1080.0)`, `iTime` with your `u_time` uniform +4. Add `uniform sampler2D u_tex;` for the captured content texture +5. Wire the uniforms to GSAP proxy values + +**Geometry ideas beyond flat planes:** + +- `SphereGeometry` — content mapped onto a globe (world map, global reach) +- `CylinderGeometry` — content on a rotating cylinder (carousel/scroll feel) +- `TorusGeometry` — content wrapped around a ring (infinity, cycle) +- `BoxGeometry` — content on a 3D box (product packaging, dice) +- GLTF models — content mapped as screen texture on phone, laptop, monitor (see `vfx-iphone-device`) + +**Post-processing stacking** (Three.js EffectComposer): + +- Bloom + film grain = cinematic +- Bloom + chromatic aberration = lens effect +- Depth of field + vignette = focused attention +- Film grain + scan lines = retro +- Multiple passes stack — add as many as you want + +**You are not limited to the effects listed here.** If you can imagine a visual treatment, you can build it. The HTML-in-Canvas API gives you the source material (any HTML rendered as a texture), and WebGL/Three.js gives you unlimited creative control over how that material is presented. diff --git a/skills/hyperframes/references/motion-principles.md b/skills/hyperframes/references/motion-principles.md index 011d59883..8e5bc2f9c 100644 --- a/skills/hyperframes/references/motion-principles.md +++ b/skills/hyperframes/references/motion-principles.md @@ -1,82 +1,90 @@ # Motion Principles -## Guardrails +## Common defaults that produce monoculture -You know these rules but you violate them. Stop. +These are the patterns LLMs reach for without thinking. None of them are wrong in isolation — they're wrong as defaults. If every scene of every video lands on the same easing, the same speed, and the same entrance direction, the compositions blur into one another no matter what the brand is. -- **Don't use the same ease on every tween.** You default to `power2.out` on everything. Vary eases like you vary font weights — no more than 2 independent tweens with the same ease in a scene. -- **Don't use the same speed on everything.** You default to 0.4-0.5s for everything. The slowest scene should be 3× slower than the fastest. Vary duration deliberately. -- **Don't enter everything from the same direction.** You default to `y: 30, opacity: 0` on every element. Vary: from left, from right, from scale, opacity-only, letter-spacing. -- **Don't use the same stagger on every scene.** Each scene needs its own rhythm. -- **Don't use ambient zoom on every scene.** Pick different ambient motion per scene: slow pan, subtle rotation, scale push, color shift, or nothing. Stillness after motion is powerful. -- **Don't start at t=0.** Offset the first animation 0.1-0.3s. Zero-delay feels like a jump cut. +- **Same ease on every tween.** `power2.out` is the most common default. Aim for variety: no more than two independent tweens sharing an ease within a scene. Eases are like font weights — vary them deliberately. +- **Same speed on every tween.** 0.4–0.5s is a common default that flattens rhythm. The slowest motion in a scene should be roughly 3× slower than the fastest. Vary duration so the eye can tell what's important. +- **Same entrance direction.** `y: 30, opacity: 0` is the universal LLM entrance. The same scene can use entrances from left, from right, from scale, from blur, opacity-only, letter-spacing — each one says something different about the element. +- **Same stagger across scenes.** Each scene should have its own rhythm. A 0.08s stagger in beat 1 and a 0.15s stagger in beat 2 makes the two beats feel like different moments. +- **Ambient zoom on every scene.** Slow-scale-up is the default ambient motion and it telegraphs "LLM-generated video." Vary the ambient motion per scene: slow pan, subtle rotation, color temperature shift, gentle drift — and sometimes nothing. Stillness after motion has real weight. +- **First animation at t=0.** Zero-delay feels like a jump cut. Offset the opening 0.1–0.3s so the scene reads as composed rather than thrown together. -## What You Don't Do Without Being Told +## Easing is emotion, not technique -### Easing is emotion, not technique +The motion is the verb. The easing is the adverb. A slide-in with `expo.out` feels confident. With `sine.inOut`, dreamy. With `elastic.out`, playful. Same motion, three different meanings. Choose the adverb deliberately. -The transition is the verb. The easing is the adverb. A slide-in with `expo.out` = confident. With `sine.inOut` = dreamy. With `elastic.out` = playful. Same motion, different meaning. Choose the adverb deliberately. +**Direction rules:** -**Direction rules — these are not optional:** +- `.out` for elements entering. Starts fast, decelerates. Feels responsive. This is the default for entrances. +- `.in` for elements leaving. Starts slow, accelerates away. Sends them off with momentum. +- `.inOut` for elements moving between positions, neither entering nor leaving the scene. -- `.out` for elements entering. Starts fast, decelerates. Feels responsive. This is your default. -- `.in` for elements leaving. Starts slow, accelerates away. Throws them off. -- `.inOut` for elements moving between positions. +Ease-in on an entrance feels sluggish. Ease-out on an exit feels reluctant. These are the most common reversals and they're worth checking your work against. -You get this backwards constantly. Ease-in for entrances feels sluggish. Ease-out for exits feels reluctant. +## Speed expresses weight -### Speed communicates weight +Duration is one of the most direct ways a composition communicates what it values. Faster motion reads as confident, urgent, kinetic — it gives the viewer less time to study what's happening, which means the work has to land in fewer frames. Slower motion reads as deliberate, considered, weighty — the viewer has time to take in the element, which means each element has to earn that attention. -- Fast (0.15-0.3s) — energy, urgency, confidence -- Medium (0.3-0.5s) — professional, most content -- Slow (0.5-0.8s) — gravity, luxury, contemplation -- Very slow (0.8-2.0s) — cinematic, emotional, atmospheric +Useful calibration ranges (not prescriptions — what a duration _expresses_ depends on what surrounds it): -### Scene structure: build / breathe / resolve +- **0.15–0.3s** — quick, percussive, kinetic. The motion reads as something happening _to_ the frame. +- **0.3–0.5s** — comfortable, professional. The motion reads as composed and reliable. +- **0.5–0.8s** — deliberate. The motion has visible weight and asks for attention. +- **0.8s+** — atmospheric. The motion becomes part of what the scene _is_, not something happening within it. -Every scene has three phases. You dump everything in the build and leave nothing for breathe or resolve. +A composition that uses only one of these ranges feels one-note. Mix them — a scene where the headline takes 0.7s to settle and the supporting details land in 0.25s creates contrast that reinforces hierarchy without needing different colors or sizes. -- **Build (0-30%)** — elements enter, staggered. Don't dump everything at once. -- **Breathe (30-70%)** — content visible, alive with ONE ambient motion. -- **Resolve (70-100%)** — exit or decisive end. Exits are faster than entrances. +## Scene structure: build, breathe, resolve -### Transitions are meaning +Every scene has three phases. The most common failure is dumping everything into the build and leaving nothing for the other two. -- **Crossfade** = "this continues" -- **Hard cut** = "wake up" / disruption -- **Slow dissolve** = "drift with me" +- **Build (0–30%)** — elements enter, staggered. Not all at once. +- **Breathe (30–70%)** — content visible, alive with one ambient motion. The viewer reads, registers, settles. +- **Resolve (70–100%)** — exit or decisive end. Exits are faster than entrances (see Asymmetry below). -You crossfade everything. Use hard cuts for disruption and register shifts. +A scene that's all build feels like a slideshow. A scene with no breathe phase doesn't let the content land. -### Choreography is hierarchy +## Transitions carry meaning -The element that moves first is perceived as most important. Stagger in order of importance, not DOM order. Don't wait for completion — overlap entries. Total stagger sequence under 500ms regardless of item count. +The transition type tells the viewer how two scenes relate: -### Asymmetry +- **Crossfade** — "this continues." Connective tissue between related ideas. +- **Hard cut** — "wake up" or a register shift. Disruption, surprise, percussive emphasis. +- **Slow dissolve** — "drift with me." Atmospheric, meditative, between-thoughts. -Entrances need longer than exits. A card takes 0.4s to appear but 0.25s to disappear. +Crossfade is the default and it's defensible most of the time. The thing to watch for is using it for everything — when every transition is a crossfade, the viewer stops registering scene changes as meaningful. Hard cuts and slow dissolves are tools for the moments where the change in scene _is_ the message. -## Visual Composition +## Choreography is hierarchy -You build for the web. Video frames are not pages. +The element that moves first is perceived as most important. Stagger in order of importance, not DOM order. Don't wait for one entrance to complete before starting the next — overlap entries. Total stagger sequence under 500ms regardless of item count keeps the scene from feeling like a slow drip. -- **Two focal points minimum per scene.** The eye needs somewhere to travel. Never a single text block floating in empty space. -- **Fill the frame.** Hero text: 60-80% of width. You will try to use web-sized elements. Don't. -- **Three layers minimum per scene.** Background treatment (glow, oversized faded type, color panel). Foreground content. Accent elements (dividers, labels, data bars). -- **Background is not empty.** Radial glows, oversized faded type bleeding off-frame, subtle border panels, hairline rules. Pure solid #000 reads as "nothing loaded." -- **Anchor to edges.** Pin content to left/top or right/bottom. Centered-and-floating is a web pattern. -- **Split frames.** Data panel on the left, content on the right. Top bar with metadata, full-width below. Zone-based layouts, not centered stacks. -- **Use structural elements.** Rules, dividers, border panels. They create paths for the eye and animate well (scaleX from 0). +## Asymmetry between entrances and exits -## Image Motion Treatment +Entrances need longer than exits. A card might take 0.4s to appear but 0.25s to disappear — entrances build presence, exits remove it, and remove takes less time than build. -Never embed a raw flat image. Every image must have motion treatment: +## Visual composition -- **Perspective tilt**: use `gsap.set(el, { transformPerspective: 1200, rotationY: -8 })` + `box-shadow` — creates depth. Do NOT use CSS `transform: perspective(...)` as GSAP will overwrite it. -- **Slow zoom (Ken Burns)**: GSAP `scale: 1` → `1.04` over beat duration — makes photos cinematic -- **Device frame**: Wrap in a laptop/phone shape using CSS `border-radius` and `box-shadow` -- **Floating UI**: Extract a key element and animate it at a different z-depth for parallax -- **Scroll reveal**: Clip the image to a viewport window and animate `y` position +Video frames are not web pages. Web layout patterns that work on a scrollable page often look broken in a fixed-frame composition. + +- **Two focal points minimum per scene.** The eye needs somewhere to travel. A single text block floating in empty space reads as unfinished. +- **Fill the frame.** Hero text typically wants 60–80% of frame width. Web type sizes — 16px body, 32px headlines — disappear at video distance. +- **Three layers minimum.** Background treatment (glow, oversized faded type, color panel), foreground content, accent elements (dividers, labels, data bars). A scene with only one layer reads flat. +- **Background is not empty.** Radial glows, oversized faded type bleeding off-frame, subtle border panels, hairline rules. Pure solid `#000` reads as "nothing loaded." +- **Anchor to edges.** Pin content to left/top or right/bottom. Centered-and-floating is a web pattern that looks lost on a 16:9 canvas. +- **Split frames.** Data panel on the left, content on the right. Top bar with metadata, full-width below. Zone-based layouts beat centered stacks. +- **Use structural elements.** Rules, dividers, border panels. They create paths for the eye and animate well (`scaleX` from 0). + +## Image motion treatment + +Embedded images shouldn't sit flat — every image earns some motion treatment: + +- **Perspective tilt** — `gsap.set(el, { transformPerspective: 1200, rotationY: -8 })` plus a `box-shadow` creates depth. Do NOT use CSS `transform: perspective(...)`; GSAP will overwrite it. +- **Slow zoom (Ken Burns)** — GSAP `scale: 1` → `1.04` over beat duration. Makes photos feel cinematic rather than pasted in. +- **Device frame** — wrap in a laptop or phone shape using `border-radius` and `box-shadow`. +- **Floating UI** — extract a key element and animate it at a different z-depth for parallax. +- **Scroll reveal** — clip the image to a viewport window and animate `y` position. ## Load-Bearing GSAP Rules @@ -132,7 +140,7 @@ Rules below came out of two independent website-to-hyperframes builds (2026-04-2 tl.to(".aura", { scale: 1.08, yoyo: true, repeat: 5, duration: 1.2 }, 0); ``` -- **Hard-kill every scene boundary, not just captions.** The caption hard-kill rule above generalizes: any element whose visibility changes at a beat boundary needs a deterministic `tl.set()` kill after its fade, because later tweens on the same element (or `immediateRender` from a sibling tween) can resurrect it. Apply to every element with an exit animation: +- **Hard-kill every scene boundary, not just captions.** The same hard-kill pattern from `captions.md` generalizes to all elements with exit animations: any element whose visibility changes at a beat boundary needs a deterministic `tl.set()` kill after its fade, because later tweens on the same element (or `immediateRender` from a sibling tween) can resurrect it. Apply to every element with an exit animation: ```js tl.to(el, { opacity: 0, duration: 0.3 }, beatEnd); diff --git a/skills/hyperframes/references/prompt-expansion.md b/skills/hyperframes/references/prompt-expansion.md index ab504eee1..e08075e52 100644 --- a/skills/hyperframes/references/prompt-expansion.md +++ b/skills/hyperframes/references/prompt-expansion.md @@ -8,12 +8,12 @@ Runs AFTER design direction is established (Step 1). The expansion consumes desi Read before generating: -- `design.md` (if it exists) — extract brand colors, fonts, mood, and constraints. The expansion cites these exact values (hex codes, font names); it does not invent new ones. +- `DESIGN.md` (if it exists) — extract brand colors, fonts, mood, and constraints. The expansion cites these exact values (hex codes, font names); it does not invent new ones. - [beat-direction.md](beat-direction.md) — per-beat planning format (concept, mood, choreography verbs, transitions, depth layers, rhythm). The expansion outputs each scene using this format. - [video-composition.md](video-composition.md) — video-medium rules for density, scale, and color presence. The expansion applies these automatically. - [../house-style.md](../house-style.md) — its rules for Background Layer (2-5 decoratives), Color, Motion, Typography apply to every scene. The expansion writes output that conforms to them. -If `design.md` doesn't exist yet, run Step 1 (Design system) first. Expansion without a design context produces generic scene breakdowns that later agents ignore. +If `DESIGN.md` doesn't exist yet, run Step 1 (Design system) first. Expansion without a design context produces generic scene breakdowns that later agents ignore. ## Why always run it @@ -40,7 +40,7 @@ Expand into a full production prompt with these sections: 1. **Title + style block** — cite design.md's exact hex values, font names, and mood. Do NOT invent a palette — quote what the design provides. -2. **Rhythm declaration** — name the scene rhythm before detailing any scene. Example: `hook-PUNCH-breathe-CTA` or `slow-build-BUILD-PEAK-breathe-CTA`. See [beat-direction.md](beat-direction.md) for rhythm templates by video type. +2. **Rhythm declaration** — name the scene rhythm before detailing any scene. Example: `hook-PUNCH-breathe-CTA` or `slow-build-BUILD-PEAK-breathe-CTA`. Derive the rhythm from the brand and the storyboard's emotional arc — see [beat-direction.md](beat-direction.md) for the considerations that drive this decision. 3. **Global rules** — parallax layers, micro-motion requirements, transition style, primary + accent transitions. Match energy to mood (calm → slow eases, high → snappy eases). diff --git a/skills/hyperframes/references/techniques.md b/skills/hyperframes/references/techniques.md index b460fa845..c9495c43c 100644 --- a/skills/hyperframes/references/techniques.md +++ b/skills/hyperframes/references/techniques.md @@ -1,8 +1,34 @@ # Visual Techniques Reference -10 proven techniques from production HyperFrames videos. Use these in your storyboard and compositions to create visually rich, professional output. Each technique includes a minimal code pattern you can adapt. +13 primitive animation techniques from production HyperFrames videos — SVG drawing, kinetic typography, variable fonts, WebGL shaders, motion-path, etc. Compose these into beats; they are the building blocks, not finished recipes. Each entry includes a minimal code pattern you can adapt. -These are NOT advanced — they're standard motion design patterns that every composition should use at least 2-3 of. +These are NOT advanced — they're standard motion design patterns that every composition should use at least 2-3 of. For pre-built UI templates (terminal chrome, device mockups, moodboard layouts), look in the `registry/blocks/` directory instead — those are recipes, not techniques. + +**These are starting points, not copy-paste templates.** Every code pattern below is a minimal working example from a real production video. Adapt them to your needs — change colors, sizes, timings, easings, element counts, layout. Combine techniques, mix parts from different patterns, invent variations. The goal is to understand the PRINCIPLE behind each technique so you can build something original, not to reproduce these examples exactly. + +## Table of Contents + +**Named text animation effects** (per-character, per-word, per-line, whole-element) with exact GSAP specs are in [`text-effects.md`](text-effects.md) — 24 bundled effects, no install needed. Use those for all headline and label animations instead of inventing timing from scratch. + +**HTML-in-Canvas patterns** (live DOM as GPU texture: iPhone/MacBook mockups, liquid glass, magnetic, portal, shatter, text cursor — using `drawElementImage` + `layoutsubtree`) are in [`html-in-canvas-patterns.md`](html-in-canvas-patterns.md) — 504 lines, one shared boilerplate + ~6 effect recipes. Use for 1–3 hero beats per video, not every beat. + +--- + +| # | Technique | What it does | Best for | +| --- | ----------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------ | +| 1 | **SVG Path Drawing** | Logos/icons draw themselves stroke by stroke | Logo reveals, diagram builds, connector lines | +| 2 | **Canvas 2D Procedural Art** | Animated noise, particles, data viz — frame-by-frame via GSAP proxy | Generative backgrounds, ambient texture | +| 3 | **CSS 3D Transforms** | Card flips, perspective grids, folding panels | Product reveals, comparison scenes | +| 4 | **Per-Word Kinetic Typography** | Text animates word-by-word with stagger timing | Thesis statements, key messages, quotes | +| 5 | **Lottie Animation** | Captured or external Lottie plays as overlay/background | Brand animations, micro-interactions | +| 6 | **Video Compositing** | Product videos play inline, masked, overlaid | Demo footage, screen recordings | +| 7 | **Character-by-Character Typing** | Terminal-style code reveals, search bar interactions | Developer tools, CLI demos | +| 8 | **Variable Font Axis Animation** | Weight, width, slant, optical size morph over time | Premium typography, brand wordmarks | +| 9 | **GSAP MotionPathPlugin** | Elements follow SVG curves, orbital motion, spirals | Dynamic entrances, connector animations | +| 10 | **Velocity-Matched Transitions** | Outgoing blur/translate matches incoming for seamless cuts | Beat transitions, scene changes | +| 11 | **Audio-Reactive Animation** | Elements pulse to narration frequency bands | Background textures, text glow, ambient motion | +| 12 | **Clip-Path Reveal Masks** | Fixed window that content slides through (text/images enter from one side) | Headline intros, image reveals, wipe effects | +| 13 | **WebGL Fragment Shader Art** | Full GPU generative backgrounds — FBM domain warp, cosine palettes | Hero backgrounds, atmospheric scenes | --- @@ -155,16 +181,22 @@ The slide distance DECAYS per word (80→12px) — mimics a camera settling. Vector animations that play inside a composition. Use for logos, character animations, icons. ```html - -. --> + + - + +``` + +Variations: `clip-path: circle(0% at 50% 50%)` → `circle(100%)` for iris reveals. `clip-path: polygon(...)` for custom shapes. + +--- + +## 13. WebGL Fragment Shader Art + +Full GPU generative backgrounds — domain-warped FBM noise, cosine palette coloring, iridescent organic patterns. Far richer than Canvas 2D. + +```html + + +``` + +Always include a Canvas 2D gradient fallback for environments without WebGL. + +--- + +## Easing Vocabulary + +GSAP offers a deep easing library. Every composition should use at least 3 different easings — using `power2.out` for everything produces flat, monotonous motion. Think of easings as tone of voice: a video that only whispers is boring; one that varies between whisper, normal, and punch is engaging. + +**The full palette** (each family has `.in`, `.out`, `.inOut` variants): + +| Family | Character | Typical use | +| -------------------- | ---------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `power1`–`power4` | Gentle (1) to aggressive (4) acceleration curves | General purpose. power2 is the workhorse, power4 for dramatic snaps | +| `back(N)` | Overshoot then settle. N controls how far past the target (1=subtle, 4=wild) | Logo reveals, badge pops, card entrances. `back.out(2.5)` for playful, `back.out(1.2)` for elegant | +| `elastic(amp, freq)` | Spring bounce. amp=magnitude, freq=oscillation speed | Panel scatter, energetic drops, fun reveals | +| `bounce` | Ball-drop bouncing | Physical interactions, icons landing, score counters | +| `expo` | Extreme acceleration curve (much steeper than power4) | Premium/luxury reveals, dramatic entrances | +| `sine` | Smooth, organic, no hard edges | Ambient float, breathing, Ken Burns, anything that loops. `.inOut` for yoyo motion | +| `circ` | Circular acceleration (starts very fast, ends very gentle or vice versa) | Camera moves, scene transitions, orbital motion | +| `steps(N)` | Discrete N-step jumps, no interpolation | Typing effects, cursor blink, counter ticks, retro/digital aesthetics | + +**Mood mapping:** Match easing character to the beat's emotional content. Smooth/organic easings (`sine`, `power1`) feel contemplative and drifting. Aggressive deceleration (`power4.out`, `expo.out`) feels snappy and confident. Spring overshoot (`back.out`) feels bouncy and physical. The storyboard's mood description should guide which character fits — not a formula. + +--- + +## Choosing techniques + +Don't match techniques to video type on autopilot — match them to the **concept of the specific beat**. Ask: what visual treatment makes this exact idea land? A beat about speed needs motion that communicates speed; a beat about precision needs geometry and structure; a beat about warmth needs texture and organic drift. -| Video energy | Techniques to combine | -| ------------------------------ | --------------------------------------------------------------- | -| High impact (launches, promos) | Per-word typography + velocity transitions + counter animations | -| Cinematic (tours, stories) | SVG path drawing + video compositing + 3D transforms | -| Technical (dev tools, APIs) | Character typing + Canvas 2D procedural + MotionPath | -| Premium (luxury, enterprise) | Variable font animation + Lottie + slow velocity transitions | -| Data-driven (stats, metrics) | Canvas 2D procedural + counter animations + SVG path drawing | +Read the storyboard beat's concept and mood, then scan this list for techniques whose _visual character_ serves that concept. Any technique can appear in any video type — the question is whether it earns its place in this beat. diff --git a/skills/hyperframes/references/text-effects.md b/skills/hyperframes/references/text-effects.md new file mode 100644 index 000000000..20d579864 --- /dev/null +++ b/skills/hyperframes/references/text-effects.md @@ -0,0 +1,151 @@ +# Text Effects — Bundled Catalog + +24 named text animation effects, bundled into HyperFrames. No separate install needed. + +**One JSON file per effect** at `skills/hyperframes/assets/text-effects/.json`. Each file is the portable contract for that effect — the per-element parameters (durations, staggers, easings, from/to keyframes, swap behavior). The shared GSAP rendering pattern (how to split text, how to stagger, how to wire to a timeline) lives once in this file, below the catalog. + +**How to use:** pick an effect that fits the brand, mood, and content. Read its `.json` to get parameters. Read the shared rendering pattern below to implement. + +--- + +## Catalog + +### Per-character + +| ID | Enter duration | Stagger | Character | +| --------------------- | -------------- | ------- | -------------------------------------------------------------------------------------------- | +| `soft-blur-in` | 700ms | 20ms | Letters fade in with a gentle upward drift and brief blur trail. Premium, atmospheric. | +| `per-character-rise` | 520ms | 18ms | Letters slide up from below baseline, no blur. Crisp, deliberate, kinetic. | +| `typewriter` | 200ms | 50ms | Stepped reveal — characters pop into existence at discrete intervals. Mechanical, editorial. | +| `bottom-up-letters` | 320ms | 65ms | Pronounced staircase rise from below. Confident, audible-feeling rhythm. | +| `top-down-letters` | 320ms | 65ms | Mirror of bottom-up — characters descend from above. | +| `stagger-from-center` | 480ms | 32ms | Middle character reveals first, edges last. Emphasizes the keyword core. | +| `stagger-from-edges` | 480ms | 32ms | Edge characters reveal first, center last. Assembles inward toward the keyword. | + +### Per-word + +| ID | Enter duration | Stagger | Character | +| ---------------------- | -------------- | ------- | ----------------------------------------------------------------------------------------------- | +| `per-word-crossfade` | 460ms | 140ms | Words fade in sequentially with short vertical drift. Calm, paced. | +| `spring-scale-in` | 280ms | 80ms | Words pop in with spring overshoot (1.06 then settle). Bouncy without being cartoonish. | +| `shared-axis-y` | 160ms | 60ms | Hard-cut word-by-word along Y axis. Sharp, editorial. | +| `blur-out-up` | 360ms | 90ms | Clean entrance, blurry rising exit. Lingers in memory rather than dismisses. | +| `kinetic-center-build` | 480ms | 220ms | Phrase builds right-to-left, line stays centered as it grows. **Layout-aware.** | +| `short-slide-right` | 560ms | 120ms | Whole line glides in from the left as one move; words reveal only by opacity. **Layout-aware.** | +| `short-slide-down` | 420ms | 160ms | Each word drops in from above and pushes the stack down. **Layout-aware.** | +| `depth-parallax-words` | 540ms | 110ms | Words enter at varied scales (0.82 → 1.0) reading as layered depth. | + +### Per-line + +| ID | Enter duration | Stagger | Character | +| -------------------- | -------------- | ------- | --------------------------------------------------------------------------- | +| `mask-reveal-up` | 580ms | 90ms | Lines clip-reveal upward through a masked viewport. Contained, masked feel. | +| `line-by-line-slide` | 640ms | 110ms | Lines slide in from left, exit to right. Flowing paragraph rhythm. | + +### Whole element + +| ID | Enter duration | Character | +| -------------------- | -------------- | ---------------------------------------------------------------------------- | +| `micro-scale-fade` | 460ms | Tiny scale (0.96 → 1.0) + fade. Barely perceptible, reads as polish. | +| `shimmer-sweep` | 680ms | Horizontal light sweep glides left-to-right. Premium, luxury polish. | +| `fade-through` | 280ms | Material-style sequential dissolve — old fades out fully, then new fades in. | +| `shared-axis-z` | 360ms | Scale-based depth swap. Outgoing recedes, incoming arrives from depth. | +| `scale-down-fade` | 380ms | Symmetric scale (1.04 → 1.0) on entrance and exit. Restrained, premium. | +| `focus-blur-resolve` | 580ms | Heavy 16px blur resolves to sharp clarity. Reads as a camera focus pull. | +| `shared-axis-x` | 380ms | Horizontal sibling transition for sequential destinations (next/prev). | + +--- + +## Shared rendering pattern (GSAP) + +All non-layout-aware effects render the same way. Implement once, then per-effect just feed in the parameters from the JSON. + +**1. Register CustomEase** (most effects use cubic-bezier strings): + +```js +gsap.registerPlugin(CustomEase); +``` + +**2. Split the text by `target`:** + +| `target` | Split rule | +| --------- | ------------------------------------------------------------------------------------------------ | +| `char` | `Array.from(text)` — preserve every character including spaces and punctuation as animated units | +| `word` | Regex `/(\S+\|\s+)/g` — span-wrap words and whitespace; animate the non-whitespace spans only | +| `line` | Split on `"\n"` — each line is a block-display span | +| `element` | No split — the whole element is the animated unit | + +Wrap each animated unit in a span (e.g., `.text-anim-unit`). For `char` and `word` targets use `display: inline-block`; for `line` use `display: block`. + +**3. Set the initial state with `gsap.set()`** from the effect's `enter.from`: + +```js +gsap.set(units, { opacity: 0, y: 14, filter: "blur(8px)" }); // values from .enter.from +``` + +**4. Animate to `enter.to` with a stagger using the effect's easing and duration:** + +```js +const ease = CustomEase.create("custom-in", "cubic-bezier(0.22, 1, 0.36, 1)"); // from enter.easing +tl.to( + units, + { + opacity: 1, + y: 0, + filter: "blur(0px)", // values from enter.to + duration: enterDurationMs / 1000, // 700ms → 0.7 + ease, + stagger: enterStaggerMs / 1000, // 20ms → 0.02 + }, + 0, +); +``` + +**5. For exit**, do the same with `exit.from` / `exit.to` / `exit.durationMs` / `exit.easing`. Place it on the timeline at `beatEnd - exit.durationMs - (units.length * exit.staggerMs) / 1000`. + +**6. Hard-kill at beat boundaries** (so a later tween or sibling resurrect doesn't bring the element back): + +```js +tl.set(units, { opacity: 0, visibility: "hidden" }, beatEnd + 0.01); +``` + +### Custom stagger order + +Two effects use ordered staggers (not DOM order): + +| Effect | `staggerOrder` value | Algorithm | +| --------------------- | -------------------- | ----------------------------------------------------------------------------------- | +| `stagger-from-center` | `center-out` | Rank = `\|index - centerIndex\|`. Ties: lower index first. | +| `stagger-from-edges` | `edges-in` | Rank = `(text.length - 1) / 2 - \|index - centerIndex\|`. Ties: higher index first. | + +GSAP's `stagger: { each, from: "center" }` handles `center-out` natively. For `edges-in`, write the function form: `stagger: { each, from: (i, target, list) => /* edges-in rank */ }`. + +### Layout-aware effects (3) + +Three effects in the catalog (`kinetic-center-build`, `short-slide-right`, `short-slide-down`) animate the LINE container's position separately from each word's reveal. Their JSON includes `layoutAware: true` plus optional `lineFrom` / `lineTo` keyframes for the container. + +Implementation pattern: + +1. Wrap all word spans in a `.text-anim-line` container. +2. Animate the line container with `lineFrom` → `lineTo` (or, for `kinetic-center-build`, with a per-word x-offset to keep the line centered as it grows). +3. Independently stagger the word spans' opacity (and any per-word transform from `enter.from` / `enter.to`). + +Each layout-aware effect's `notes` field in its JSON tells you which line-level transform to apply. Don't infer. + +--- + +## In the storyboard + +Every text element in every beat names an effect by ID. Not "headline fades in" — read the catalog, pick the effect that fits the brand and beat, and name the specific ID. + +Format: + +```markdown +**Text Animations:** + +- [element, e.g. "main headline"]: `[effect-id]` — skills/hyperframes/assets/text-effects/[id].json +- [element, e.g. "eyebrow label"]: `[effect-id]` — skills/hyperframes/assets/text-effects/[id].json +- [element, e.g. "body copy 3 lines"]: `[effect-id]` — skills/hyperframes/assets/text-effects/[id].json +``` + +Sub-agents read the named JSON and implement using the shared rendering pattern above. No creative invention needed — just parameter substitution. diff --git a/skills/hyperframes/references/transitions.md b/skills/hyperframes/references/transitions.md index 3b5404bba..3737583b3 100644 --- a/skills/hyperframes/references/transitions.md +++ b/skills/hyperframes/references/transitions.md @@ -11,50 +11,61 @@ These are non-negotiable for every multi-scene composition: 3. **Exit animations are BANNED** except on the final scene. Do NOT use `gsap.to()` to animate elements out before a transition fires. The transition IS the exit. Outgoing scene content must be fully visible when the transition starts — the transition handles the visual handoff. 4. **Final scene exception:** The last scene MAY fade elements out (e.g., fade to black at the end of the composition). This is the only scene where exit animations are allowed. -## Energy → Primary Transition +## Energy → Transition Character -| Energy | CSS Primary | Shader Primary | Accent | Duration | Easing | -| ---------------------------------------- | ---------------------------- | ------------------------------------ | ------------------------------ | --------- | ---------------------- | -| **Calm** (wellness, brand story, luxury) | Blur crossfade, focus pull | Cross-warp morph, thermal distortion | Light leak, circle iris | 0.5-0.8s | `sine.inOut`, `power1` | -| **Medium** (corporate, SaaS, explainer) | Push slide, staggered blocks | Whip pan, cinematic zoom | Squeeze, vertical push | 0.3-0.5s | `power2`, `power3` | -| **High** (promos, sports, music, launch) | Zoom through, overexposure | Ridged burn, glitch, chromatic split | Staggered blocks, gravity drop | 0.15-0.3s | `power4`, `expo` | +The energy of a beat tells you what motion character the transition should have — not which specific transition to use. The motion character is a quality you derive from the brand and content, then find a transition that has that quality. -Pick ONE primary (60-70% of scene changes) + 1-2 accents. Never use a different transition for every scene. +**Soft/organic character:** transitions that breathe, dissolve, or drift. Nothing sharp, mechanical, or percussive. Duration 0.5–0.8s, smooth easing curves. -## Mood → Transition Type +**Directional/purposeful character:** transitions that move content decisively. Clear direction, readable momentum. Duration 0.3–0.5s, clean deceleration. -Think about what the transition _communicates_, not just what it looks like. +**Percussive/instant character:** transitions that hit like a cut. Immediate, almost hard-cut energy. Duration 0.15–0.3s, aggressive or near-instant easing. -| Mood | Transitions | Why it works | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | -| **Warm / inviting** | Light leak, blur crossfade, focus pull, film burn · **Shader:** thermal distortion, light leak, cross-warp morph | Soft edges, warm color washes. Nothing sharp or mechanical. | -| **Cold / clinical** | Squeeze, zoom out, blinds, shutter, grid dissolve · **Shader:** gravitational lens | Content transforms mechanically — compressed, shrunk, sliced, gridded. | -| **Editorial / magazine** | Push slide, vertical push, diagonal split, shutter · **Shader:** whip pan | Like turning a page or slicing a layout. Clean directional movement. | -| **Tech / futuristic** | Grid dissolve, staggered blocks, blinds, chromatic aberration · **Shader:** glitch, chromatic split | Grid dissolve is the core "data" transition. Shader glitch adds posterization + scan lines. | -| **Tense / edgy** | Glitch, VHS, chromatic aberration, ripple · **Shader:** ridged burn, glitch, domain warp | Instability, distortion, digital breakdown. Ridged burn adds sharp lightning-crack edges. | -| **Playful / fun** | Elastic push, 3D flip, circle iris, morph circle, clock wipe · **Shader:** ripple waves, swirl vortex | Overshoot, bounce, rotation, expansion. Swirl vortex adds organic spiral distortion. | -| **Dramatic / cinematic** | Zoom through, zoom out, gravity drop, overexposure, color dip to black · **Shader:** cinematic zoom, gravitational lens, domain warp | Scale, weight, light extremes. Shader transitions add per-pixel depth. | -| **Premium / luxury** | Focus pull, blur crossfade, color dip to black · **Shader:** cross-warp morph, thermal distortion | Restraint. Cross-warp morph flows both scenes into each other organically. | -| **Retro / analog** | Film burn, light leak, VHS, clock wipe · **Shader:** light leak | Organic imperfection. Warm color bleeds, scan line displacement. | +These are calibration ranges, not recipes. A brand that treats its "high energy" section with restraint might use 0.4s for a moment that another brand transitions in 0.2s — both are correct for their brand. Pick ONE character that defines the video's primary transitions, then use 1–2 contrasting moments as intentional accents. See the **Mood → Motion Quality** section below to find transitions with the right character for a given mood. + +## Mood → Motion Quality + +Think about what the transition _communicates_, not what it looks like. The question is: **what motion quality serves this mood?** Then find transitions that have that quality in the catalog (`transitions/catalog.md`). + +| Mood | Motion quality that fits | Why | +| ------------------------ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | +| **Warm / inviting** | Soft edges, dissolving, color-temperature washes — nothing sharp, mechanical, or percussive | Warmth reads as continuity and flow; hard cuts or compression feel cold | +| **Cold / clinical** | Mechanical transformation — compression, slicing, gridding, precision | The content appears to be processed or structured, reinforcing a systematic quality | +| **Editorial / magazine** | Clean directional movement — like turning a page | Feels like content is being browsed or curated, not revealed | +| **Tech / futuristic** | Data-like fragmentation, digital displacement, scan artifacts | Transition feels computational rather than physical | +| **Tense / edgy** | Instability, distortion, displacement — something slightly wrong about the image | Introduces friction where smooth transitions would release tension | +| **Playful / fun** | Overshoot, expansion, rotation — motion with personality and bounce | Transitions that feel like objects rather than effects | +| **Dramatic / cinematic** | Scale, weight, light extremes — the cut is an event, not a bridge | Every shader and every hard cut carries narrative gravity | +| **Premium / luxury** | Restraint — transitions that are barely visible, or invisible | Luxury communicates through what it withholds | +| **Retro / analog** | Organic imperfection — light bleed, scan lines, color wash | Physical film artifacts; imperfection as authenticity | + +Use this table to derive what **quality** the transition should have, then look at the specific options in `transitions/catalog.md` to find one that has that quality for this brand. The transitions listed in the catalog are all available; none are reserved for a specific mood. ## Narrative Position -| Position | Use | Why | -| -------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------- | -| **Opening** | Your most distinctive transition. Match the mood. 0.4-0.6s | Sets the visual language for the entire piece. | -| **Between related points** | Your primary transition. Consistent. 0.3s | Don't distract — the content is continuing. | -| **Topic change** | Something different from your primary. Staggered blocks, shutter, squeeze. | Signals "new section" — the viewer's brain resets. | -| **Climax / hero reveal** | Your boldest accent. Fastest or most dramatic. | This is the payoff — spend your best transition here. | -| **Wind-down** | Return to gentle. Blur crossfade, crossfade. 0.5-0.7s | Let the viewer exhale after the climax. | -| **Outro** | Slowest, simplest. Crossfade, color dip to black. 0.6-1.0s | Closure. Don't introduce new energy at the end. | +Each position in the video has a different job to do. What transition you pick for each should come from the brand's motion character and the storyboard's intent — not from a rule about "climax = boldest." + +- **Opening** — establishes the motion language for the entire video. Make a deliberate choice; whatever you pick here sets the viewer's expectation for everything that follows. +- **Between related points** — should be almost invisible. The content is continuing; the transition shouldn't draw attention to itself. Consistency matters more than distinctiveness here. +- **Topic change** — needs enough contrast from your primary that it signals "something different is starting." The contrast is in motion character, not just duration. +- **Climax / hero reveal** — this is the moment the video has been building to. The transition should feel earned by what came before. "Use your boldest transition here" is a default, not a rule — the climax of a restrained editorial piece might be a hard cut. +- **Wind-down** — returns to a motion character that allows the viewer to exhale. Matches the opening in tone, not necessarily in technique. +- **Outro** — no new energy. Slowest and simplest in the video. Closure. + +## Blur and Motion Intensity + +Blur and duration should express the energy of the content, not match a lookup table. The ranges below are calibration references — starting points to adjust from based on what the brand and storyboard call for. + +Higher-energy transitions: shorter duration, less blur, no hold at peak. The motion is immediate. +Lower-energy transitions: longer duration, more blur, longer hold at peak. The motion has weight. + +Calibration ranges (not prescriptions): -## Blur Intensity by Energy +- Soft/organic: blur 20–30px, duration 0.8–1.2s, hold 0.3–0.5s +- Directional/purposeful: blur 8–15px, duration 0.4–0.6s, hold 0.1–0.2s +- Percussive/instant: blur 3–6px, duration 0.2–0.3s, no hold -| Energy | Blur | Duration | Hold at peak | -| ---------- | ------- | -------- | ------------ | -| **Calm** | 20-30px | 0.8-1.2s | 0.3-0.5s | -| **Medium** | 8-15px | 0.4-0.6s | 0.1-0.2s | -| **High** | 3-6px | 0.2-0.3s | 0s | +A brand that uses these as a formula will produce transitions that feel the same across every video. A brand-derived choice asks: what blur and duration expresses the weight this transition should have? ## Presets @@ -92,7 +103,22 @@ CSS transitions animate scene containers with opacity, transforms, clip-path, an **Both are first-class options.** Shaders are provided by the `@hyperframes/shader-transitions` package — import from the package instead of writing raw GLSL. CSS transitions are simpler to set up. Choose based on the effect you want, not based on which is easier. -When a composition uses shader transitions, ALL transitions in that composition should be shader-based (the WebGL canvas replaces DOM-based scene switching). Don't mix CSS and shader transitions in the same composition. +**Mixing is supported.** You can have some transitions use WebGL shaders and others use a CSS crossfade in the same composition. Omit the `shader` field on any `TransitionConfig` entry to get a smooth opacity crossfade instead of a WebGL effect: + +```js +var tl = HyperShader.init({ + bgColor: "#000", + accentColor: "#6366f1", + scenes: ["s1", "s2", "s3", "s4"], + transitions: [ + { time: 4.0, shader: "sdf-iris", duration: 0.7 }, // WebGL shader + { time: 8.5, duration: 0.8 }, // no shader → CSS crossfade + { time: 13.0, shader: "domain-warp", duration: 0.6 }, // WebGL shader + ], +}); +``` + +HyperShader manages all scene visibility regardless of transition type. Let it create the timeline (don't pass `timeline:` into `init()`) and add your beat animations to the returned `tl` after the call. ## Shader-Compatible CSS Rules diff --git a/skills/website-to-hyperframes/references/step-3-storyboard.md b/skills/website-to-hyperframes/references/step-3-storyboard.md index 2d516962a..314d3cce0 100644 --- a/skills/website-to-hyperframes/references/step-3-storyboard.md +++ b/skills/website-to-hyperframes/references/step-3-storyboard.md @@ -322,10 +322,10 @@ Every text element in this beat must name a specific effect from `skills/hyperfr Format (FORMAT EXAMPLES of structure, not prescriptions — pick based on brand/mood/context): -- `[element — e.g. "main headline"]`: `[effect-id]` — `skills/hyperframes/assets/text-effects/effects/[id].json` -- `[element — e.g. "eyebrow label"]`: `[effect-id]` — `skills/hyperframes/assets/text-effects/effects/[id].json` +- `[element — e.g. "main headline"]`: `[effect-id]` — `skills/hyperframes/assets/text-effects/[id].json` +- `[element — e.g. "eyebrow label"]`: `[effect-id]` — `skills/hyperframes/assets/text-effects/[id].json` -The sub-agent reads the named JSON file and implements from `showcase.library_adapters.gsap`. No creative decisions at build time. +The sub-agent reads the named JSON file for the per-effect parameters (durations, staggers, easings, from/to keyframes) and implements using the shared GSAP rendering pattern in `text-effects.md`. No creative decisions at build time — just parameter substitution. ### Beat Timing