mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 23:29:50 +00:00
feat(skills): c2v mining pass — 7 new blueprints, 10 new rules, compacted recipe corpus (#2680)
* feat(skills): c2v mining pass over animation blueprints and rules Compacts ~45 existing animation rules/blueprints into tighter recipe form (net -3.4k lines) and adds 17 mined from the c2v corpus: - 7 blueprints: agent-progress-theater, camera-journey, fixed-anchor-cycle, panel-edit-live-sync, prompt-type-submit-generate, transcript-scroll-artifact-reveal, zoom-out-workspace-reveal - 10 rules: 3d-camera-flight, anchored-layout-expand, chart-scrub-readout, chromatic-glitch, control-target-sync, cursor-drag, gradient-text-sweep, multi-cursor-choreography, particle-burst, theme-crossfade-morph Both indexes updated. * feat(skills): sync product-launch script bank with mined blueprint roles The role->blueprint script bank in product-launch-video/story-design.md is kept 1:1 with blueprints-index role declarations, which the c2v mining pass expanded. Adds the 25 missing entries (script-shape descriptor + example lines + pattern): 13 for the 7 new blueprints, 12 for role widenings on 7 existing ones (cursor-ui-demo, dataviz-countup, titlecard-reveal, et al.), and states the 1:1 sync contract in the bank's intro. * docs(skills): cover constellation-hub scatter-drift variant in the script bank Review follow-up on #2680: the SOCIAL_PROOF constellation-hub entry patterned only the orbit shape; the c2v pass added a scatter-drift end-card variant with the opposite geometry (no hub, no ring). Adds an example line and extends the pattern so a scatter-drift beat's VO isn't steered toward the orbit shape.
This commit is contained in:
@@ -7,43 +7,25 @@ metadata:
|
||||
|
||||
# Kinetic Beat Slam
|
||||
|
||||
Short phrases hit one at a time on a **steady beat**, each with a _different_ entrance, then stack into a locked finale. This is the recipe for "punchy / rhythmic" text-forward pieces (taglines, manifestos, hype intros). The difference between generic and rhythmic is (1) one shared **onset array** driving every element, (2) **distinct** entrances per phrase rather than one reused helper, and (3) optional **rhythm chrome** that visibly keeps the beat.
|
||||
Short phrases hit one at a time on a **steady beat**, each with a _different_ entrance, then stack into a locked finale — the recipe for "punchy / rhythmic" text-forward pieces (taglines, manifestos, hype intros). The difference between generic and rhythmic is (1) one shared **onset array** driving every element, (2) **distinct** entrances per phrase rather than one reused helper, and (3) optional **rhythm chrome** that visibly keeps the beat.
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Define the beat once.** A single `BEATS = [t0, t1, t2, …]` array (seconds) is the rhythmic spine. Every phrase entrance, accent, and chrome tick reads its time from this array — so the whole piece locks to one pulse instead of drifting hand-tuned offsets.
|
||||
2. **Vary the entrances.** Phrase 1 slams (scale + blur), phrase 2 snaps from the side, phrase 3 rises and rotates. Same _energy_, different _form_ — reusing one `punchIn()` for all three reads as flat.
|
||||
3. **Land a finale.** All phrases lock into a left-aligned or centered stack; an accent underline sweeps in; optionally a continuous low-amplitude pulse holds the last beat.
|
||||
A single tempo grid — `PULSE` seconds per sub-beat, `BEATS = [t0, t1, t2, …]` on that grid — is the rhythmic spine; every phrase entrance, accent, and chrome tick reads its time from it, so the piece locks to one pulse instead of drifting hand-tuned offsets. Each phrase gets a different transform axis (scale+blur slam / side snap / rise+rotate) with short attacks (0.35–0.6s on the hit), then the stack holds with a finite low-amplitude breath.
|
||||
|
||||
## Beat & Easing
|
||||
|
||||
Pick the entrance easing by attack character (the choice is discrete):
|
||||
|
||||
| GSAP ease | Attack feel |
|
||||
| ------------- | ------------------------------------------- |
|
||||
| `power4.out` | Hard slam, fast settle ⭐ default for a hit |
|
||||
| `expo.out` | Hardest snap (side-snaps, whip-ins) |
|
||||
| `back.out(2)` | Overshoot pop — accents, not body words |
|
||||
| `circ.out` | Heavy rise with momentum |
|
||||
|
||||
Use **at least 3 distinct easings** across the piece (entrances are its "tone of voice"). Keep durations short — 0.35–0.6s on the hit, ≤0.25s on the exit — so the beat stays percussive.
|
||||
|
||||
## HTML
|
||||
## Recipe
|
||||
|
||||
```html
|
||||
<section class="clip" data-start="0" data-duration="15" data-track-index="1">
|
||||
<div class="kbs-stage">
|
||||
<div class="kbs-line" id="p1"><span class="verb">Notice</span> more.</div>
|
||||
<div class="kbs-line" id="p2"><span class="verb">Decide</span> faster.</div>
|
||||
<div class="kbs-line" id="p3"><span class="verb">Act</span> now.</div>
|
||||
</div>
|
||||
<!-- optional rhythm chrome -->
|
||||
<div class="kbs-metronome" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</section>
|
||||
<!-- inside a standard scene clip (hyperframes-core) -->
|
||||
<div class="kbs-stage">
|
||||
<div class="kbs-line" id="p1"><span class="verb">Notice</span> more.</div>
|
||||
<div class="kbs-line" id="p2"><span class="verb">Decide</span> faster.</div>
|
||||
<div class="kbs-line" id="p3"><span class="verb">Act</span> now.</div>
|
||||
</div>
|
||||
<!-- optional rhythm chrome -->
|
||||
<div class="kbs-metronome" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></div>
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
```css
|
||||
.kbs-stage {
|
||||
position: absolute;
|
||||
@@ -51,9 +33,7 @@ Use **at least 3 distinct easings** across the piece (entrances are its "tone of
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 120px 160px; /* title-safe margin */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.kbs-line {
|
||||
font-family: "Archivo Black", "League Gothic", sans-serif; /* embedded display face */
|
||||
@@ -61,11 +41,10 @@ Use **at least 3 distinct easings** across the piece (entrances are its "tone of
|
||||
line-height: 0.96;
|
||||
letter-spacing: -0.03em;
|
||||
color: #f5f5f5;
|
||||
will-change: transform, filter, opacity;
|
||||
}
|
||||
.kbs-line .verb {
|
||||
color: #ff5b2e;
|
||||
} /* one accent hue */
|
||||
color: #ff5b2e; /* exactly one accent hue */
|
||||
}
|
||||
.kbs-metronome {
|
||||
position: absolute;
|
||||
bottom: 64px;
|
||||
@@ -82,102 +61,77 @@ Use **at least 3 distinct easings** across the piece (entrances are its "tone of
|
||||
}
|
||||
```
|
||||
|
||||
## GSAP Timeline
|
||||
```js
|
||||
// ONE tempo grid drives everything — phrases AND the metronome read it.
|
||||
const PULSE = 0.4; // seconds per sub-beat
|
||||
const BEATS = [PULSE * 1, PULSE * 5, PULSE * 9]; // phrase onsets, on the grid
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||||
<script>
|
||||
window.__timelines = window.__timelines || {};
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
// Distinct entrances per phrase (NOT one reused helper).
|
||||
tl.fromTo(
|
||||
"#p1",
|
||||
{ scale: 1.5, filter: "blur(16px)", opacity: 0 },
|
||||
{ scale: 1, filter: "blur(0px)", opacity: 1, duration: 0.5, ease: "power4.out" },
|
||||
BEATS[0],
|
||||
);
|
||||
tl.fromTo(
|
||||
"#p2",
|
||||
{ x: -320, opacity: 0 },
|
||||
{ x: 0, opacity: 1, duration: 0.45, ease: "expo.out" },
|
||||
BEATS[1],
|
||||
);
|
||||
tl.fromTo(
|
||||
"#p3",
|
||||
{ y: 90, rotation: 6, opacity: 0 },
|
||||
{ y: 0, rotation: 0, opacity: 1, duration: 0.55, ease: "circ.out" },
|
||||
BEATS[2],
|
||||
);
|
||||
|
||||
// ONE tempo grid drives everything — phrases AND the metronome read it (no scattered offsets).
|
||||
const PULSE = 0.4; // seconds per sub-beat (the grid)
|
||||
const BEATS = [PULSE * 1, PULSE * 5, PULSE * 9]; // phrase onsets, on the grid
|
||||
// Rhythm chrome: each tick flashes on the SAME grid, not a magic offset.
|
||||
gsap.utils.toArray(".kbs-metronome i").forEach((tick, i) => {
|
||||
tl.to(tick, { opacity: 1, duration: 0.08, yoyo: true, repeat: 1, ease: "none" }, PULSE * (i + 1));
|
||||
});
|
||||
|
||||
// Distinct entrances per phrase (NOT one reused helper).
|
||||
tl.fromTo(
|
||||
"#p1",
|
||||
{ scale: 1.5, filter: "blur(16px)", opacity: 0 },
|
||||
{ scale: 1, filter: "blur(0px)", opacity: 1, duration: 0.5, ease: "power4.out" },
|
||||
BEATS[0],
|
||||
);
|
||||
tl.fromTo(
|
||||
"#p2",
|
||||
{ x: -320, opacity: 0 },
|
||||
{ x: 0, opacity: 1, duration: 0.45, ease: "expo.out" },
|
||||
BEATS[1],
|
||||
);
|
||||
tl.fromTo(
|
||||
"#p3",
|
||||
{ y: 90, rotation: 6, opacity: 0 },
|
||||
{ y: 0, rotation: 0, opacity: 1, duration: 0.55, ease: "circ.out" },
|
||||
BEATS[2],
|
||||
);
|
||||
|
||||
// Rhythm chrome: each metronome tick flashes on the SAME grid (PULSE), not a magic offset.
|
||||
const ticks = gsap.utils.toArray(".kbs-metronome i");
|
||||
ticks.forEach((tick, i) => {
|
||||
tl.to(
|
||||
tick,
|
||||
{ opacity: 1, duration: 0.08, yoyo: true, repeat: 1, ease: "none" },
|
||||
PULSE * (i + 1),
|
||||
);
|
||||
});
|
||||
|
||||
// Finale hold: a low-amplitude breath on the locked stack.
|
||||
// floor (not ceil) so the repeat never overshoots data-duration; max(0,…) so a short hold
|
||||
// never yields a negative repeat (GSAP treats negative repeat as -1 = infinite = non-deterministic).
|
||||
const holdStart = BEATS[2] + 0.7,
|
||||
cycle = 1.6,
|
||||
holdDur = 15 - holdStart;
|
||||
tl.to(
|
||||
".kbs-stage",
|
||||
{
|
||||
scale: 1.01,
|
||||
duration: cycle / 2,
|
||||
ease: "sine.inOut",
|
||||
yoyo: true,
|
||||
repeat: Math.max(0, Math.floor(holdDur / cycle) - 1),
|
||||
},
|
||||
holdStart,
|
||||
);
|
||||
|
||||
window.__timelines["main"] = tl;
|
||||
</script>
|
||||
// Finale hold: floor (not ceil) so the repeat never overshoots data-duration;
|
||||
// max(0,…) so a short hold never yields a negative repeat (GSAP reads negative as -1 = infinite).
|
||||
const holdStart = BEATS[2] + 0.7,
|
||||
cycle = 1.6,
|
||||
holdDur = SCENE_DURATION - holdStart;
|
||||
tl.to(
|
||||
".kbs-stage",
|
||||
{
|
||||
scale: 1.01,
|
||||
duration: cycle / 2,
|
||||
ease: "sine.inOut",
|
||||
yoyo: true,
|
||||
repeat: Math.max(0, Math.floor(holdDur / cycle) - 1),
|
||||
},
|
||||
holdStart,
|
||||
);
|
||||
```
|
||||
|
||||
## How to Choose Values
|
||||
## Variations
|
||||
|
||||
- **BEATS spacing** — 1.2–1.8s between hits reads as a confident beat; <0.8s feels frantic, >2.5s loses the pulse. Keep spacing even (it's a _beat_).
|
||||
- **Entrance duration** — 0.35–0.6s. The hit must resolve before the next beat.
|
||||
- **Distinct entrances** — assign a different transform axis per phrase (scale / x / y+rotate). Reuse the _ease family_, vary the _motion_.
|
||||
- **Accent hue** — exactly one (the verbs). The rest is mono white/near-black.
|
||||
- **Rhythm chrome** — optional but high-impact for "rhythmic": a 5-tick metronome, a center beat bar, or a `// label` monospace tag pulsing on-beat. Mark any decorative that must survive a shader transition per `../../transitions/overview.md` rules.
|
||||
- **Entrance easing by attack character** — `power4.out` hard slam ⭐ default hit · `expo.out` hardest snap (side-snaps, whip-ins) · `back.out(2)` overshoot pop (accents only, not body words) · `circ.out` heavy rise with momentum. Use **at least 3 distinct easings** across the piece.
|
||||
- **Rhythm chrome alternatives** — a center beat bar or a `// label` monospace tag pulsing on-beat instead of the 5-tick metronome; mark any decorative that must survive a shader transition per `../../transitions/overview.md`.
|
||||
- **Finale dressing** — stack + accent underline sweep ([css-marker-patterns](css-marker-patterns.md)); don't just leave the last phrase sitting.
|
||||
|
||||
## Key Principles
|
||||
## Values
|
||||
|
||||
- **One beat array, not scattered offsets** — every element times off `BEATS[]`. This is the single biggest lever for "rhythmic."
|
||||
- **Different entrance per phrase** — a reused `punchIn()` for all lines is the flat-but-competent tell.
|
||||
- **Short attacks** — percussive means fast in, brief, decisive. Long fades kill the beat.
|
||||
- **One accent hue, heavy weight** — embedded display faces (Archivo Black, League Gothic, Oswald) at 150px+; see `hyperframes-creative/references/typography.md`.
|
||||
- **Finale earns the hold** — stack + underline sweep + optional breath; don't just leave the last phrase sitting.
|
||||
| token | range | notes |
|
||||
| ----------------- | -------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| BEATS spacing | 1.2–1.8s | <0.8s frantic, >2.5s loses the pulse; keep spacing even — it's a beat |
|
||||
| entrance duration | 0.35–0.6s | the hit must resolve before the next beat; exits ≤0.25s |
|
||||
| accent hue | exactly 1 | the verbs; the rest mono white / near-black |
|
||||
| display face | 150px+, heavy weight | Archivo Black / League Gothic / Oswald — see `hyperframes-creative/references/typography.md` |
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
- **Timeline paused**: `gsap.timeline({ paused: true })`. Never `tl.play()`.
|
||||
- **No infinite repeats** on the hold/chrome — use `repeat: Math.max(0, Math.floor(dur / cycle) - 1)` (no `repeat: -1`). Use **`Math.floor`, not `Math.ceil`** — `ceil` overshoots `data-duration` and trips the `gsap_repeat_ceil_overshoot` lint rule; the `Math.max(0, …)` guards against a negative repeat (which GSAP reads as `-1` = infinite = non-deterministic) when the hold is shorter than two cycles.
|
||||
- **No banned exit animations** between scenes — if this is one of several scenes, the _transition_ is the exit (see `../../transitions/overview.md`); only a final scene may fade out.
|
||||
- **Display font must be embedded** or it silently falls back at render (Anton/Bebas-as-literal are NOT embedded — `Bebas Neue` aliases to League Gothic; verify in `typography.md`).
|
||||
- **Registry key = `data-composition-id`** on the root.
|
||||
- **One beat array, not scattered offsets** — every element times off `BEATS[]` / `PULSE`; this is the single biggest lever for "rhythmic".
|
||||
- **Different entrance per phrase** — a reused `punchIn()` for all lines is the flat-but-competent tell. Vary the motion axis, reuse the ease _family_.
|
||||
- **Finale repeat math**: `repeat: Math.max(0, Math.floor(dur / cycle) - 1)` — `Math.ceil` overshoots `data-duration` and trips the `gsap_repeat_ceil_overshoot` lint rule; a negative repeat is read by GSAP as `-1` (infinite).
|
||||
- **No banned exit animations between scenes** — in a montage the _transition_ is the exit (`../../transitions/overview.md`); only a final scene may fade out.
|
||||
- **Display font must be embedded** or it silently falls back at render — Anton / Bebas-as-literal are NOT embedded (`Bebas Neue` aliases to League Gothic; verify in `typography.md`).
|
||||
|
||||
## Combinations
|
||||
## See also
|
||||
|
||||
- [3d-text-depth-layers.md](3d-text-depth-layers.md) — extruded depth on the slammed words
|
||||
- [css-marker-patterns.md](css-marker-patterns.md) — underline sweep / circle on the finale
|
||||
- [sine-wave-loop.md](sine-wave-loop.md) — the finale breath/pulse
|
||||
|
||||
## Pairs with HF skills
|
||||
|
||||
- `/hyperframes-animation` — timeline + easing vocabulary (`../../adapters/gsap-easing-and-stagger.md`)
|
||||
- `/hyperframes-creative` — `references/video-composition.md` (foreground rhythm chrome), `references/typography.md` (embedded display fonts)
|
||||
- `/hyperframes-core` — composition wiring, determinism (finite repeats)
|
||||
`3d-text-depth-layers` (extruded depth on the slammed words) · `css-marker-patterns` (finale underline/circle) · `sine-wave-loop` (the finale breath) · `../adapters/gsap-easing-and-stagger.md` (easing vocabulary).
|
||||
|
||||
Reference in New Issue
Block a user