Files
heygen-com--hyperframes/skills/hyperframes-animation/rules/multi-phase-camera.md
T
wehub-resource-sync 85453da49f
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
Docs / Validate docs (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Sync skills to ClawHub / Publish changed skills (push) Waiting to run
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00

10 KiB
Raw Blame History

name, description, metadata
name description metadata
multi-phase-camera Sequential camera zoom with 2-3 distinct phases (pull-back / focus / push) plus continuous micro-drift for organic cinematic feel.
tags
camera, zoom, phase, drift, scale, cinematic

Multi-Phase Camera

A camera wrapper around the entire scene that progresses through discrete zoom phases at scripted triggers. Continuous sine-driven micro-drift overlays so the camera never feels static between phases. Distinct from a single linear zoom — multi-phase creates "cinematic pacing" (anticipation → reveal → settle).

How It Works

The camera is a single wrapping <div> whose transform: scale() translate(x, y) is driven by:

  1. Phase scale — a stepwise scale value that advances through phases at trigger times (e.g. PHASE_1_SCALE at t=0 → PHASE_2_SCALE at PHASE_2_AT → PHASE_3_SCALE at PHASE_3_AT)
  2. Drift offset — a continuous sine-based translateX / translateY (small amplitude, slow frequency) ADDED to the phase transform

Both run inside the GSAP timeline so HF seeks frame-by-frame deterministically.

HTML

<div
  class="scene"
  data-composition-id="cam-scene"
  data-start="0"
  data-duration="6"
  data-track-index="0"
>
  <div class="camera" id="camera">
    <div class="content">
      <div class="hero">{Brand}</div>
      <div class="tagline">{tagline}</div>
      <div class="cta">{ctaText}</div>
    </div>
  </div>
</div>

CSS

.scene {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: {sceneBgColor};
}
.camera {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  transform-origin: 50% 50%;
  will-change: transform;
}
.content {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  text-align: center;
}
.hero {
  font-family: {font};
  font-weight: 900;
  font-size: {heroSize};
  letter-spacing: 8px;
  color: {textColor};
  text-transform: uppercase;
}
.tagline {
  font-family: {font};
  font-weight: 600;
  font-size: {taglineSize};
  color: {accentColor};
}
.cta {
  font-family: {monoFont};
  font-weight: 700;
  font-size: {ctaSize};
  letter-spacing: 6px;
  color: {accentColor};
  text-transform: uppercase;
}

GSAP Timeline

<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 camera = document.getElementById("camera");

  // Three-phase scale plan: pullback → focus → push
  const phase = { scale: PHASE_1_SCALE };

  // Phase 1 — start pulled back
  // (no tween needed for the initial value; set via the phase object)

  // Phase 2 — settle to neutral focus
  tl.to(
    phase,
    {
      scale: PHASE_2_SCALE,
      duration: PHASE_2_DUR,
      ease: PHASE_2_EASE,
    },
    PHASE_2_AT,
  );

  // Phase 3 — slow push-in for the climax
  tl.to(
    phase,
    {
      scale: PHASE_3_SCALE,
      duration: PHASE_3_DUR,
      ease: PHASE_3_EASE,
    },
    PHASE_3_AT,
  );

  // Drift driver — continuous sine motion overlaid on the phase scale
  const drift = { p: 0 };

  tl.to(
    drift,
    {
      p: Math.PI * 2 * DRIFT_CYCLES,
      duration: TOTAL_DURATION,
      ease: "none",
      onUpdate: () => {
        const dx = Math.sin(drift.p) * DRIFT_AMP_X;
        const dy = Math.sin(drift.p * DRIFT_FREQ_RATIO) * DRIFT_AMP_Y;
        camera.style.transform = `scale(${phase.scale}) translate(${dx}px, ${dy}px)`;
      },
    },
    0,
  );

  // Content reveals (entry beats inside the camera frame)
  tl.from(".hero", { opacity: 0, y: 32, scale: 0.96, duration: 0.9, ease: "power3.out" }, HERO_AT);
  tl.from(".tagline", { opacity: 0, y: 16, duration: 0.7, ease: "power3.out" }, TAGLINE_AT);
  tl.from(".cta", { opacity: 0, y: 8, duration: 0.7, ease: "power3.out" }, CTA_AT);

  window.__timelines["cam-scene"] = tl;
</script>

How to Choose Values

  • PHASE_1_SCALE / PHASE_2_SCALE / PHASE_3_SCALE — three-step zoom values

    • Range: PHASE_1 0.880.96; PHASE_2 0.981.02; PHASE_3 1.041.15
    • Effects: tighter spread = subtler camera; wider = more cinematic
    • Constraints: at PHASE_1_SCALE < 1, .scene MUST have overflow: hidden or the inner content's edges leak outside the frame
  • PHASE_2_AT / PHASE_2_DUR — when the focus phase starts and how long it takes

    • Range: PHASE_2_AT 0.31.0 s; PHASE_2_DUR 1.01.8 s
    • Effects: longer DUR = slower settle, more cinematic
  • PHASE_3_AT / PHASE_3_DUR — when the push phase starts and how long it takes

    • Range: PHASE_3_AT 2.04.0 s; PHASE_3_DUR 1.02.0 s
    • Constraints: PHASE_3_AT must be ≥ PHASE_2_AT + PHASE_2_DUR (otherwise focus is preempted)
  • PHASE_2_EASE / PHASE_3_EASE — ease per transition

    • Discrete choice: power2.out, power3.out, power2.inOut
    • Selection: cinematic feel; spring/back easing on a camera feels uncomfortable. Each later phase should imply more settling than the previous (longer dur OR more out-easing).
  • TOTAL_DURATION — composition's total runtime (matches data-duration)

    • Reference: the drift tween must span the whole composition
  • DRIFT_CYCLES — number of sine cycles across TOTAL_DURATION

    • Range: 13
    • Effects: 1 = one slow breath; 3 = noticeably busier
    • Constraints: high values read as mechanical wobble rather than organic drift
  • DRIFT_AMP_X / DRIFT_AMP_Y — peak drift offset in pixels

    • Range: DRIFT_AMP_X 28 px; DRIFT_AMP_Y 14 px
    • Effects: per-frame imperceptible, visible over time. If drift is a discrete shake, it's too much.
  • DRIFT_FREQ_RATIO — multiplier on the Y-axis sine frequency

    • Range: 1.21.5
    • Effects: 1.0 = perfect diagonal (reads mechanical); ~1.3 = organic Lissajous
  • HERO_AT / TAGLINE_AT / CTA_AT — content reveal beats

    • Constraints: HERO_AT should land AFTER PHASE_1 settles via PHASE_2 (otherwise the hero feels like it's flying away while camera is still pulling back)

Phase Patterns

Pattern Scale Sequence (Phase 1 → 2 → 3) Feel When to use
Focus-in back → neutral → slight push Approach → settle → slight push Default product reveal
Dramatic reveal push → neutral → pull Wide → focus → settle back Hero shot with breathing room
Steady push neutral → slight push → more push Gradual forward momentum Continuous narrative push
Bookend pull neutral → strong push → neutral Settle → push → release CTA emphasis then release

Variations

Phase trigger by content beat (not time)

If the composition has content phases (e.g. an entry completes, then orbit starts), align the camera tween start time with the content tween's end time rather than using a fixed clock value.

Camera shake (panic / impact)

For a brief shake instead of drift, replace the drift tween with a higher-amplitude, higher-frequency one over a short window:

tl.to(
  drift,
  {
    p: Math.PI * 2 * SHAKE_CYCLES,
    duration: SHAKE_DUR,
    ease: "none",
    onUpdate: () => {
      const dx = Math.sin(drift.p) * SHAKE_AMP_X;
      const dy = Math.sin(drift.p * SHAKE_FREQ_RATIO) * SHAKE_AMP_Y;
      camera.style.transform = `scale(${phase.scale}) translate(${dx}px, ${dy}px)`;
    },
  },
  SHAKE_AT,
);

Targeted zoom into off-center element

If the climax should zoom into a non-centered element, combine scale with counter-translation. Compute the offset so the target ends at viewport center after scale:

const target = document.querySelector(".cta");
const tRect = target.getBoundingClientRect();
const viewportCenter = { x: STAGE_W / 2, y: STAGE_H / 2 };
const offsetX = (viewportCenter.x - (tRect.left + tRect.width / 2)) / phase.scale;
const offsetY = (viewportCenter.y - (tRect.top + tRect.height / 2)) / phase.scale;
// then in onUpdate: translate(offsetX + dx, offsetY + dy)

Key Principles

  • Drift is imperceptible per-frame, visible over time — if drift reads as discrete shake, the amplitude is too high
  • Drift X and Y at slightly different frequenciesDRIFT_FREQ_RATIO ≈ 1.3 prevents perfect-diagonal motion, which reads as mechanical
  • Phase springs softer than UI springspower2.inOut or power3.out for cinematic feel; spring/back easing on a camera feels uncomfortable
  • Each later phase settles "deeper" — phase 2 ease should imply more settling than phase 1 (longer duration OR more out-easing). Wakes up → settles → settles deeper
  • Camera wraps EVERYTHING in the scene — applying camera per-element creates parallax bugs and breaks "this is one viewpoint"
  • overflow: hidden on .scene — phases that pull back (scale < 1) reveal edges of the inner content. Without overflow: hidden, those edges leak outside the stage frame and HF renders them as visible content
  • Hero reveal starts AFTER initial pullback ease lands — if the camera is still pulling back when the headline fades in, the headline feels like it's flying away

Critical Constraints

  • Timeline must be paused: gsap.timeline({ paused: true })
  • Registry key = data-composition-id
  • No CSS transition on .camera — competes with the GSAP transform
  • transform-origin: 50% 50% on camera — off-center origin creates unpredictable phase-to-phase drift
  • will-change: transform on .camera — the camera transform updates every frame
  • overflow: hidden on .scene — required when any phase scale < 1
  • Scene background on .scene, not .camera — if background is on camera, scaling/translating it reveals the outer void

Combinations

Pairs with HF skills

  • /hyperframes-animation — multi-phase tween + drift onUpdate
  • /hyperframes-core — composition wiring, scene wrapper
  • /hyperframes-clihyperframes lint