mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 15:20:13 +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,299 +7,127 @@ metadata:
|
||||
|
||||
# Ambient Glow Bloom
|
||||
|
||||
A soft radial glow that **blooms in behind a hero element** (card, logo, metric) and holds, giving it presence. Unlike `press-release-spring`'s click-triggered burst or `asr-keyword-glow`'s word-timed envelope, this glow is **un-triggered** — it simply blooms on the hero's settle and stays lit. Two forms: a **hero bloom** that swells behind a settling element and then breathes, and a **traveling glow sweep** that translates a soft highlight across a surface exactly once. Both are finite, deterministic, and seek-safe.
|
||||
A soft radial glow that **blooms in behind a hero element** (card, logo, metric) and holds, giving it presence. Unlike `press-release-spring`'s click-triggered burst or `asr-keyword-glow`'s word-timed envelope, this glow is **un-triggered** — it blooms on the hero's settle and stays lit. Two forms: a **hero bloom** that swells behind a settling element then breathes, and a **traveling sweep** that translates a soft highlight across a surface exactly once.
|
||||
|
||||
## How It Works
|
||||
|
||||
A radial-gradient layer sits **behind** the hero (`z-index` below it), starting at `opacity: 0`. Over the bloom-in window it ramps `opacity: 0 → peak` with a gentle `scale` swell (the halo "inflates" into place), timed to land on the hero's settle so the two read as one beat.
|
||||
A radial-gradient layer sits **behind** the hero (glow `z-index: 1`, hero `z-index: 2` — a glow in front occludes it), starting at `opacity: 0`. Over the bloom-in window it ramps `opacity: 0 → peak` with a gentle `scale` swell, timed so `BLOOM_START + BLOOM_DUR` lands on the hero's settle — glow and hero resolve as ONE beat ("powering on"), never glow-then-card. After bloom-in:
|
||||
|
||||
Two forms diverge after bloom-in:
|
||||
1. **Hero bloom** — a **bounded idle breathe** during the hold: a finite `ease: "none"` tween advances a `phase` proxy and `onUpdate` nudges opacity + scale a hair around peak (never a `yoyo` loop). `sin(0) = 0` → the breathe starts exactly at the bloom's resting state.
|
||||
2. **Traveling sweep** — a narrow highlight band at one edge translates **once** across to the other (`x` off-surface to off-surface), clipped to the surface (`overflow: hidden`). One pass, no return — a repeating sweep reads as a loading shimmer, not a reveal accent (the shimmer-sweep variation below is the sanctioned exception).
|
||||
|
||||
1. **Hero bloom** — once lit, the glow does a **bounded idle breathe** during the hold. Drive it with an `onUpdate` reading `tl.time()` (NOT a `repeat: -1` yoyo): a `Math.sin` of elapsed time nudges `opacity` and `scale` a hair around their peak. At `sin(0) = 0` the breathe starts exactly at the bloom's resting state — no jump.
|
||||
2. **Traveling sweep** — a narrow highlight gradient at one edge of the surface translates **once** across to the other edge (`x` from off-surface to off-surface), a single finite pass. No loop, no return. The sweep layer is clipped to the surface so the highlight only reads where it overlaps.
|
||||
Peak opacity stays restrained (**≤ 0.45 hard ceiling**) so the glow gives presence without washing the frame; the glow color is **darker + more saturated** than the element it backs (a same-hue, same-lightness glow disappears into the surface).
|
||||
|
||||
Peak opacity stays restrained (≤ ~0.45) so the glow gives presence without washing the frame; the glow color is darker / more saturated than the element it backs.
|
||||
|
||||
## HTML
|
||||
## Recipe
|
||||
|
||||
```html
|
||||
<div
|
||||
class="scene"
|
||||
id="bloom-scene"
|
||||
data-composition-id="bloom-scene"
|
||||
data-start="0"
|
||||
data-duration="DURATION"
|
||||
data-track-index="0"
|
||||
>
|
||||
<div class="bloom-stage">
|
||||
<!-- Glow layer sits BEHIND the hero -->
|
||||
<div class="bloom-glow" id="bloom-glow"></div>
|
||||
<div class="hero-card" id="hero-card">{HeroLabel}</div>
|
||||
</div>
|
||||
<!-- inside a standard scene clip -->
|
||||
<div class="bloom-stage">
|
||||
<div class="bloom-glow" id="bloom-glow"></div>
|
||||
<!-- z-index: 1; inset: GLOW_INSET (negative); background: {glowGradient} -->
|
||||
<div class="hero-card" id="hero-card">{HeroLabel}</div>
|
||||
<!-- z-index: 2 -->
|
||||
</div>
|
||||
<!-- sweep form: <div class="sweep" id="sweep"> inside the overflow:hidden surface -->
|
||||
```
|
||||
|
||||
For the traveling-sweep form, the sweep layer is clipped to the surface it crosses:
|
||||
|
||||
```html
|
||||
<div class="surface" id="surface">
|
||||
<!-- grid / wordmark / card content here -->
|
||||
<div class="sweep" id="sweep"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## CSS
|
||||
|
||||
```css
|
||||
.scene {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: {bgColor};
|
||||
}
|
||||
.bloom-stage {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.hero-card {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: HERO_WIDTH;
|
||||
height: HERO_HEIGHT;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: {heroBg};
|
||||
border-radius: HERO_RADIUS;
|
||||
font-family: {font};
|
||||
font-weight: 900;
|
||||
font-size: HERO_FONT_SIZE;
|
||||
color: {heroTextColor};
|
||||
}
|
||||
.bloom-glow {
|
||||
/* Radial halo behind the hero — extends past it via negative inset */
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
inset: GLOW_INSET;
|
||||
background: {glowGradient};
|
||||
opacity: 0;
|
||||
transform: scale(GLOW_START_SCALE);
|
||||
transform-origin: 50% 50%;
|
||||
pointer-events: none;
|
||||
/* will-change because opacity + scale both animate during bloom AND breathe */
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
/* Traveling-sweep form */
|
||||
.surface {
|
||||
position: relative;
|
||||
overflow: hidden; /* clips the sweep to the surface footprint */
|
||||
border-radius: SURFACE_RADIUS;
|
||||
}
|
||||
.sweep {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
/* A narrow soft band, wider than it needs to be so the falloff is gentle */
|
||||
width: SWEEP_WIDTH;
|
||||
/* Diagonal highlight: angle the gradient so the sweep reads as raked light */
|
||||
background: {sweepGradient};
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
```
|
||||
|
||||
## GSAP Timeline
|
||||
|
||||
```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 });
|
||||
|
||||
const glow = document.getElementById("bloom-glow");
|
||||
|
||||
// ── Form A: HERO BLOOM ──────────────────────────────────────────────
|
||||
// Phase 1 — bloom in. opacity 0 → peak + gentle scale swell, landing on
|
||||
// the hero's settle. ease "power2.out" so it arrives soft, not snappy.
|
||||
tl.fromTo(
|
||||
glow,
|
||||
{ opacity: 0, scale: GLOW_START_SCALE },
|
||||
{
|
||||
opacity: GLOW_PEAK_OPACITY,
|
||||
scale: 1,
|
||||
duration: BLOOM_DUR,
|
||||
ease: "power2.out",
|
||||
```js
|
||||
// ── Form A: HERO BLOOM ── bloom in soft, landing on the hero's settle.
|
||||
tl.fromTo(
|
||||
"#bloom-glow",
|
||||
{ opacity: 0, scale: GLOW_START_SCALE },
|
||||
{ opacity: GLOW_PEAK_OPACITY, scale: 1, duration: BLOOM_DUR, ease: "power2.out" },
|
||||
BLOOM_START,
|
||||
);
|
||||
// Bounded breathe during the hold — finite phase tween, NOT a yoyo loop.
|
||||
const glow = document.getElementById("bloom-glow");
|
||||
const phase = { p: 0 };
|
||||
tl.to(
|
||||
phase,
|
||||
{
|
||||
p: Math.PI * 2 * BREATHE_CYCLES,
|
||||
duration: BREATHE_DUR,
|
||||
ease: "none",
|
||||
onUpdate: () => {
|
||||
const s = Math.sin(phase.p);
|
||||
glow.style.opacity = String(GLOW_PEAK_OPACITY + s * OPACITY_AMP);
|
||||
glow.style.transform = `scale(${1 + s * SCALE_AMP})`;
|
||||
},
|
||||
BLOOM_START,
|
||||
);
|
||||
},
|
||||
BLOOM_START + BLOOM_DUR,
|
||||
);
|
||||
|
||||
// Phase 2 — bounded idle breathe during the hold. NOT a yoyo loop:
|
||||
// a single finite tween advances `phase` 0 → 2π·CYCLES; onUpdate reads it
|
||||
// and nudges opacity + scale a hair around their peak.
|
||||
// sin(0) = 0 → breathe starts exactly at the bloom's resting state.
|
||||
const phase = { p: 0 };
|
||||
tl.to(
|
||||
phase,
|
||||
{
|
||||
p: Math.PI * 2 * BREATHE_CYCLES,
|
||||
duration: BREATHE_DUR,
|
||||
ease: "none",
|
||||
onUpdate: () => {
|
||||
const s = Math.sin(phase.p);
|
||||
glow.style.opacity = String(GLOW_PEAK_OPACITY + s * OPACITY_AMP);
|
||||
glow.style.transform = `scale(${1 + s * SCALE_AMP})`;
|
||||
},
|
||||
},
|
||||
BLOOM_START + BLOOM_DUR, // breathe begins right as the bloom settles
|
||||
);
|
||||
|
||||
// ── Form B: TRAVELING SWEEP ─────────────────────────────────────────
|
||||
// A single finite pass. opacity fades in, x travels from off one edge to
|
||||
// off the other, opacity fades out as it exits. One pass — no repeat.
|
||||
const sweep = document.getElementById("sweep");
|
||||
tl.fromTo(
|
||||
sweep,
|
||||
{ x: SWEEP_START_X, opacity: 0 },
|
||||
{
|
||||
x: SWEEP_END_X,
|
||||
opacity: SWEEP_PEAK_OPACITY,
|
||||
duration: SWEEP_DUR,
|
||||
ease: "none", // constant glide reads as a raking light, not an ease-in object
|
||||
},
|
||||
SWEEP_START,
|
||||
);
|
||||
// Fade the trailing edge out so it doesn't pop off at the surface edge.
|
||||
tl.to(sweep, { opacity: 0, duration: SWEEP_FADE_DUR, ease: "power1.in" }, SWEEP_FADE_START);
|
||||
|
||||
window.__timelines["bloom-scene"] = tl;
|
||||
</script>
|
||||
// ── Form B: TRAVELING SWEEP ── one finite pass, constant glide.
|
||||
tl.fromTo(
|
||||
"#sweep",
|
||||
{ x: SWEEP_START_X, opacity: 0 },
|
||||
{ x: SWEEP_END_X, opacity: SWEEP_PEAK_OPACITY, duration: SWEEP_DUR, ease: "none" },
|
||||
SWEEP_START,
|
||||
);
|
||||
tl.to("#sweep", { opacity: 0, duration: SWEEP_FADE_DUR, ease: "power1.in" }, SWEEP_FADE_START);
|
||||
```
|
||||
|
||||
## Variations
|
||||
|
||||
### Bloom-and-hold (no breathe)
|
||||
- **Bloom-and-hold** — for scenes <3s or a hero with its own idle, skip the breathe: the single `fromTo` is the whole recipe.
|
||||
- **Pulse-on-arrival** — bloom slightly PAST peak (`GLOW_OVERSHOOT_OPACITY`, `scale: 1.06`), then a second adjacent tween eases down to a steady hold — one breath punctuating the landing, no ongoing loop.
|
||||
- **Multi-hero relay** — stagger per-glow `BLOOM_START` by ~0.15–0.3s across a row; shrink `OPACITY_AMP` / `SCALE_AMP` per the `/√N` rule below.
|
||||
- **Diagonal raked sweep** — angle `{sweepGradient}` (~105°) across a wordmark: the classic one-pass logo sheen. Narrower `SWEEP_WIDTH`, higher `SWEEP_PEAK_OPACITY`.
|
||||
|
||||
For very short scenes (< 3s) or when the hero already has its own idle, skip Phase 2 entirely — bloom to peak and hold flat. The single `fromTo` is the whole recipe; the glow is just lit presence.
|
||||
### Shimmer sweep (text-clipped status-phrase working-state)
|
||||
|
||||
### Pulse-on-arrival (one swell, then settle to a lower hold)
|
||||
|
||||
Bloom slightly **past** peak, then ease back down to a steady hold level — a single breath that punctuates the hero's landing without an ongoing loop. Two adjacent tweens (state continuity, same as `press-release-spring`):
|
||||
The sweep re-aimed **inside type**: a soft highlight gradient clipped into a status phrase ("Thinking…", "Analyzing dataset…") via `background-clip: text` travels left→right through the letterforms — the grey-on-grey shimmer that says _still working_. Unlike every other form here it legitimately **repeats while the status is live**: the repetition is diegetic working-state, not idle wobble (same defense as a blinking caret — the motion performs status). Two things keep it honest: it is **bounded** (one finite tween whose pass count is computed from the status window, never `repeat: -1`), and it is **killed at resolve** — the moment the status completes, the shimmer stops dead; a shimmer surviving into the answer beat turns a working indicator into decoration.
|
||||
|
||||
```js
|
||||
tl.fromTo(
|
||||
glow,
|
||||
{ opacity: 0, scale: GLOW_START_SCALE },
|
||||
{ opacity: GLOW_OVERSHOOT_OPACITY, scale: 1.06, duration: BLOOM_DUR, ease: "power2.out" },
|
||||
BLOOM_START,
|
||||
);
|
||||
// Status shimmer — N passes as ONE bounded tween. Killed at resolve.
|
||||
const status = document.getElementById("status-phrase");
|
||||
// CSS on #status-phrase: background: {shimmerGradient}; background-size: 300% 100%;
|
||||
// -webkit-background-clip: text; background-clip: text; color: transparent;
|
||||
const shimmer = { p: 0 };
|
||||
const PASSES = Math.round(STATUS_DUR / PASS_PERIOD); // whole passes, computed up front
|
||||
tl.to(
|
||||
glow,
|
||||
{ opacity: GLOW_HOLD_OPACITY, scale: 1, duration: SETTLE_DUR, ease: "power2.inOut" },
|
||||
BLOOM_START + BLOOM_DUR,
|
||||
shimmer,
|
||||
{
|
||||
p: PASSES,
|
||||
duration: STATUS_DUR,
|
||||
ease: "none",
|
||||
onUpdate: () => {
|
||||
const t = shimmer.p % 1; // 0→1 within each pass; percent axis inverted → left→right travel
|
||||
status.style.backgroundPosition = `${(1 - t) * 100}% 50%`;
|
||||
},
|
||||
},
|
||||
STATUS_START,
|
||||
);
|
||||
tl.set(status, { backgroundPosition: "100% 50%" }, STATUS_START + STATUS_DUR); // resolve: dead.
|
||||
```
|
||||
|
||||
### Multi-hero relay (staggered blooms behind a row of cards)
|
||||
Keep it a whisper: `{shimmerGradient}` is the status text's own grey with one slightly-lighter band (highlight stop a step above the base, nothing near white); `background-size` ~300% keeps the band narrow in the glyphs; `PASS_PERIOD` 1.2–1.8s — slower reads as a sheen accent, faster as a spinner. Whole-number `PASSES` lands the band at its start position exactly at the kill frame, so the `tl.set` is visually a no-op. This is the working-state cousin of `gradient-text-sweep`: reach **here** when the sweep _means_ "in progress," **there** when the gradient is the typographic treatment itself.
|
||||
|
||||
Bloom each card's glow on a stagger so presence sweeps across the row. Per-glow `BLOOM_START` offset by `STAGGER` (~0.15-0.3s); shrink `OPACITY_AMP` / `SCALE_AMP` per the concurrent-elements rule below so N breathing halos don't compound into a shimmer.
|
||||
## Values
|
||||
|
||||
### Diagonal raked sweep (wordmark sheen)
|
||||
|
||||
Angle `{sweepGradient}` (e.g. a 105° linear gradient) and let the band travel left→right across a wordmark or logo lockup. Reads as light raking across a surface — the classic one-pass logo sheen. Same single-pass timeline; just a narrower `SWEEP_WIDTH` and a higher `SWEEP_PEAK_OPACITY` since it's a tight highlight on a small target.
|
||||
|
||||
## How to Choose Values
|
||||
|
||||
### Glow geometry
|
||||
|
||||
- **GLOW_INSET** — negative inset so the radial halo extends past the hero edges.
|
||||
- Range: `-200` to `-450` px on a 1920×1080 canvas; larger halo for a bigger hero
|
||||
- Effects: too small and the glow is a tight rim, not ambient presence
|
||||
- **GLOW_START_SCALE** — scale at the start of bloom-in (the halo "inflates" to 1).
|
||||
- Range: 0.80 (clear inflation) → 0.92 (subtle) → 1.0 (no swell, opacity-only bloom)
|
||||
- Constraints: keep ≤ 1.0 — the swell should grow into place, not shrink
|
||||
|
||||
### Bloom-in dynamics
|
||||
|
||||
- **BLOOM_DUR** — bloom-in duration.
|
||||
- Range: 0.6-1.4s; longer for a hero that's still settling so they land together
|
||||
- Effects: shorter → the glow "snaps on"; longer → it suffuses in (the ambient feel)
|
||||
- **BLOOM_START** — when the bloom begins.
|
||||
- Constraints: align so `BLOOM_START + BLOOM_DUR` ≈ the hero's settle frame, so glow and hero resolve as one beat — not glow-then-card or card-then-glow
|
||||
- **GLOW_PEAK_OPACITY** — peak halo opacity.
|
||||
- Range: 0.15 (subtle) → 0.30 (default) → 0.45 (dramatic)
|
||||
- **Constraints: ≤ 0.45** — higher washes the whole frame and the hero loses contrast against its own glow
|
||||
|
||||
### Idle breathe (hero-bloom form)
|
||||
|
||||
- **BREATHE_DUR** — breathe tween length.
|
||||
- Constraints: equals `TOTAL_DURATION − (BLOOM_START + BLOOM_DUR)` to fill the hold with motion
|
||||
- **BREATHE_CYCLES** — number of full breaths across `BREATHE_DUR`.
|
||||
- Range: `BREATHE_DUR / 4s ≤ CYCLES ≤ BREATHE_DUR / 2.5s` (a 2.5-4s breath period reads as a slow ambient pulse — glow breathing wants to be slower than element breathing)
|
||||
- **OPACITY_AMP** — sine amplitude on opacity around the peak.
|
||||
- **Default: 0.02-0.05** (barely-perceptible pulse — the right answer for most scenes)
|
||||
- Constraints: `GLOW_PEAK_OPACITY + OPACITY_AMP` must stay ≤ 0.45
|
||||
- **SCALE_AMP** — sine amplitude on the halo scale.
|
||||
- **Default: 0.01-0.03** (the halo "breathes" without visibly resizing)
|
||||
- Push higher only when the glow is the sole motion in a short isolated scene
|
||||
|
||||
### Traveling sweep
|
||||
|
||||
- **SWEEP_WIDTH** — width of the soft highlight band.
|
||||
- Range: 15-35% of the surface width (a wide soft band) for a grid sheen; 8-15% for a tight wordmark sheen
|
||||
- **SWEEP_START_X / SWEEP_END_X** — travel endpoints, both fully off-surface.
|
||||
- Constraints: start ≈ `-(SWEEP_WIDTH + edge)`, end ≈ `surfaceWidth + edge` — the band must enter from fully off one edge and exit fully off the other, so there's no visible spawn/despawn mid-surface
|
||||
- **SWEEP_DUR** — single-pass travel duration.
|
||||
- Range: 0.8-1.6s; one deliberate pass, slow enough to read as light, fast enough not to dominate
|
||||
- **SWEEP_PEAK_OPACITY** — highlight opacity.
|
||||
- Range: 0.10 (whisper sheen) → 0.25 (default) → 0.40 (bright rake)
|
||||
- Constraints: ≤ ~0.45 (same wash limit); tighter sweeps tolerate the high end
|
||||
- **SWEEP_START / SWEEP_FADE_START / SWEEP_FADE_DUR** — when the pass runs and tails out.
|
||||
- Constraints: `SWEEP_FADE_START + SWEEP_FADE_DUR ≈ SWEEP_START + SWEEP_DUR` so opacity reaches 0 exactly as the band clears the far edge
|
||||
|
||||
### Tokens
|
||||
|
||||
- **{glowGradient}** — radial-gradient, saturated near center fading to transparent. Color should be **darker + more saturated** than `{heroBg}` — a same-color glow looks washed out (same rule as `press-release-spring`'s burst).
|
||||
- **{sweepGradient}** — a soft band: `transparent → highlight → transparent`. For a sheen, a near-white or brand-tint highlight at low alpha; angle it (e.g. `linear-gradient(105deg, …)`) for a raked look.
|
||||
- **{heroBg} / {heroTextColor}** — the hero surface the glow backs; high contrast so the lit hero still reads against its halo.
|
||||
|
||||
## Key Principles
|
||||
|
||||
- **Un-triggered by design** — this glow does NOT wait on a click (`press-release-spring`) or a word timestamp (`asr-keyword-glow`). It blooms on the hero's settle as ambient presence. If you need a triggered burst, reach for one of those rules instead.
|
||||
- **Glow behind, hero in front** — glow `z-index: 1`, hero `z-index: 2`. A glow in front occludes the hero at peak opacity.
|
||||
- **Glow color darker + more saturated than the element** — bright hero → dark, saturated halo. A same-hue, same-lightness glow disappears into the surface.
|
||||
- **Land glow and hero as ONE beat** — time `BLOOM_START + BLOOM_DUR` to the hero's settle. A glow that arrives before or after the card reads as two separate events; arriving together reads as the card "powering on."
|
||||
- **Restrained peak — default to the LOW end.** `GLOW_PEAK_OPACITY` 0.15-0.30 for most scenes; 0.45 is a hard ceiling. A glow you consciously notice is too strong — it should register as the hero having weight, not as a visible light source.
|
||||
- **Breathe is BOUNDED, never a loop** — the idle pulse is a finite `onUpdate` tween reading `tl.time()` (via the `phase` proxy), not `repeat: -1` / `yoyo`. `sin(0) = 0` means it starts at the bloom's resting state with no jump. (Same reason as `sine-wave-loop`: an infinite/CSS loop desyncs from the HF seek clock.)
|
||||
- **Sweep is ONE pass** — the traveling highlight enters off one edge and exits off the other a single time. No return trip, no loop. A repeating sweep reads as a loading shimmer, not a one-time reveal accent.
|
||||
- **Concurrent halos compound** — N breathing glows in a row add up. Per-glow `OPACITY_AMP` and `SCALE_AMP` ≤ default `/ √N`, and stagger the breathe period (2.6s / 2.9s / 3.3s) so they don't pulse in lockstep. (Same `/√N` discipline as `sine-wave-loop`'s concurrent-elements rule.)
|
||||
- **Don't combine `boxShadow` glow on the hero with this halo layer** — they compete in the layout pipeline and the result reads muddy. Put the glow on the dedicated `.bloom-glow` layer, not as a shadow on the hero.
|
||||
| token | range / default | notes |
|
||||
| ----------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- |
|
||||
| GLOW_PEAK_OPACITY | 0.15 (subtle) → 0.30 (default) → **0.45 hard ceiling** | higher washes the frame; a glow you consciously notice is too strong |
|
||||
| GLOW_INSET | −200 to −450px (1920×1080) | negative so the halo extends past the hero; too small reads as a tight rim |
|
||||
| GLOW_START_SCALE | 0.80–1.0 | ≤1.0 — grow into place, never shrink |
|
||||
| BLOOM_DUR / BLOOM_START | 0.6–1.4s | `BLOOM_START + BLOOM_DUR` ≈ the hero's settle frame |
|
||||
| OPACITY_AMP / SCALE_AMP | 0.02–0.05 / 0.01–0.03 default | `PEAK + OPACITY_AMP ≤ 0.45`; push only when the glow is the sole motion |
|
||||
| BREATHE_CYCLES | period 2.5–4s per breath | glow breathes slower than element breathing |
|
||||
| SWEEP_WIDTH | 15–35% of surface (grid) / 8–15% (wordmark) | |
|
||||
| SWEEP_DUR | 0.8–1.6s | one deliberate pass — slow enough to read as light |
|
||||
| SWEEP_PEAK_OPACITY | 0.10 → 0.25 (default) → 0.40 | same ≤ ~0.45 wash limit; tight sweeps tolerate the high end |
|
||||
| SWEEP_START_X / END_X | fully off-surface both ends | no visible spawn/despawn mid-surface; fade reaches 0 as the band clears |
|
||||
| PASS_PERIOD (shimmer) | 1.2–1.8s | with whole-number PASSES |
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
- **Timeline must be paused**: `gsap.timeline({ paused: true })`
|
||||
- **Registry key = `data-composition-id`**
|
||||
- **No CSS `transition`** on the glow / sweep — interpolates independently of HF seek and flickers
|
||||
- **No `repeat` / `yoyo` / `repeat: -1`** — the breathe is a bounded finite tween; the sweep is one pass
|
||||
- **No `Math.random` / `Date.now`** — the breathe phase is deterministic (`phase.p` over a fixed duration)
|
||||
- **GSAP transform aliases only**: `x`, `y`, `scale`, `rotation` — plus `opacity` / `filter`. Never tween `width` / `height` / `left` / `top` (the halo swell is `scale`, the sweep travel is `x`).
|
||||
- **`will-change: transform, opacity`** on the glow — it animates both during bloom-in and the breathe
|
||||
- **Glow peak `opacity ≤ 0.45`** — higher washes the composition
|
||||
- **Sweep endpoints fully off-surface** — band must enter and exit beyond the clipped edges so it never spawns/despawns mid-frame
|
||||
- **Glow peak opacity ≤ 0.45** — including breathe amplitude; default to the LOW end (0.15–0.30).
|
||||
- **Glow behind, hero in front**; glow color darker + more saturated than the hero surface.
|
||||
- **Land glow and hero as one beat** — before or after reads as two separate events.
|
||||
- **Breathe is bounded, sweep is one pass** — the only sanctioned repetition is the shimmer sweep, bounded and killed at resolve.
|
||||
- **Concurrent halos compound** — per-glow amps ≤ default `/√N`, stagger breathe periods (2.6s / 2.9s / 3.3s) so they don't pulse in lockstep.
|
||||
- **Don't combine a `boxShadow` glow on the hero with this halo layer** — they compete and read muddy; the glow lives on the dedicated layer.
|
||||
|
||||
## Combinations
|
||||
## See also
|
||||
|
||||
- [sine-wave-loop.md](sine-wave-loop.md) — pair the hero-bloom form with a sine breathe on the hero element itself; the glow breathes on opacity, the hero breathes on scale/y, slightly out of phase for a layered "alive" hold
|
||||
- [press-release-spring.md](press-release-spring.md) — distinct sibling: that rule's `bg-glow` is **click-triggered**, this one is un-triggered. Don't run both behind the same element
|
||||
- [counting-dynamic-scale.md](counting-dynamic-scale.md) — bloom the accent halo behind the hero stat card on the count-up's settle (the `dataviz-countup` blueprint's "soft accent glow blooms behind the hero metric" beat)
|
||||
- [stat-bars-and-fills.md](stat-bars-and-fills.md) — glow blooms behind the hero metric + its paired graphic as they land together
|
||||
- [center-outward-expansion.md](center-outward-expansion.md) — run the traveling-sweep across the assembled layout once it resolves (the `grid-card-assemble` blueprint's "traveling-glow sweep across the assembled grid")
|
||||
|
||||
## Pairs with HF skills
|
||||
|
||||
- `/hyperframes-animation` — `onUpdate` writing opacity/transform + bounded sine breathe
|
||||
- `/hyperframes-core` — composition wiring
|
||||
- `/hyperframes-cli` — `hyperframes lint`
|
||||
`sine-wave-loop` (hero breathes on scale/y while the glow breathes on opacity, out of phase) · `press-release-spring` (the click-triggered sibling — never both behind one element) · `counting-dynamic-scale` / `stat-bars-and-fills` (bloom behind a landing stat) · `center-outward-expansion` (sweep across the assembled grid) · `gradient-text-sweep` (the design-beat gradient counterpart).
|
||||
|
||||
Reference in New Issue
Block a user