Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ Thumbs.db
# non-generated assets (logos, svgs) that should stay in the repo.
docs/images/

videos/

# IDE
.vscode/
.idea/
Expand Down Expand Up @@ -74,6 +76,7 @@ examples/*
!examples/k8s-jobs
!examples/k8s-jobs/**
packages/studio/data/

.desloppify/
.worktrees/

Expand Down Expand Up @@ -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/
Expand Down
3 changes: 2 additions & 1 deletion skills/hyperframes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/blur-out-up.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/bottom-up-letters.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/depth-parallax-words.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/fade-through.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/focus-blur-resolve.json
Original file line number Diff line number Diff line change
@@ -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.'"
}
23 changes: 23 additions & 0 deletions skills/hyperframes/assets/text-effects/kinetic-center-build.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/line-by-line-slide.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/mask-reveal-up.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/micro-scale-fade.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/per-character-rise.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/per-word-crossfade.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/scale-down-fade.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/shared-axis-x.json
Original file line number Diff line number Diff line change
@@ -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."
}
22 changes: 22 additions & 0 deletions skills/hyperframes/assets/text-effects/shared-axis-y.json
Original file line number Diff line number Diff line change
@@ -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."
}
20 changes: 20 additions & 0 deletions skills/hyperframes/assets/text-effects/shared-axis-z.json
Original file line number Diff line number Diff line change
@@ -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."
}
Loading
Loading