mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
Beat-driven speed ramp, freeze-frame hit, and hard cut for music-led promos and montages. Contributed as #2957; applied here as content so the whole catalog set lands together. Verified by rendering: 6s, clean, deterministic (the one Math.random hit is a comment above a fixed pattern array). Opaque root, so no demo.html is needed for its catalog preview. Co-authored-by: akiyoshisan1218 <akiyoshisan1218@users.noreply.github.com>
880 lines
27 KiB
HTML
Vendored
880 lines
27 KiB
HTML
Vendored
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=1920, height=1080" />
|
||
<title>Beat Freeze Cut</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link
|
||
href="https://fonts.googleapis.com/css2?family=Inter:wght@500;600;700;800;900&display=block"
|
||
rel="stylesheet"
|
||
/>
|
||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
|
||
<style>
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html,
|
||
body {
|
||
width: 1920px;
|
||
height: 1080px;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
background: #0b0d10;
|
||
}
|
||
|
||
/*
|
||
EDIT / STUDIO PARAMS
|
||
────────────────────
|
||
Studio color params (CSS custom properties on the root):
|
||
--bg-color default #0B0D10
|
||
--accent-color default #00E5C7
|
||
|
||
Numeric / string params (beat times, labels, intensity) are NOT in the
|
||
formal registry params schema today (only color keys are used by
|
||
existing blocks). Edit the CONFIG object in the script below instead.
|
||
*/
|
||
|
||
#bfc-root {
|
||
position: relative;
|
||
width: 1920px;
|
||
height: 1080px;
|
||
overflow: hidden;
|
||
--bg-color: #0b0d10;
|
||
--accent-color: #00e5c7;
|
||
--bfc-ink: #f4f6f8;
|
||
--bfc-muted: #a0a8b0;
|
||
--bfc-panel: #14181f;
|
||
--bfc-panel-2: #1a2029;
|
||
--bfc-line: rgba(255, 255, 255, 0.08);
|
||
font-family:
|
||
"Inter",
|
||
system-ui,
|
||
-apple-system,
|
||
sans-serif;
|
||
color: var(--bfc-ink);
|
||
}
|
||
|
||
#bfc-bg {
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(ellipse 80% 60% at 50% 40%, rgba(0, 229, 199, 0.07) 0%, transparent 55%),
|
||
radial-gradient(
|
||
ellipse 50% 40% at 80% 80%,
|
||
rgba(255, 255, 255, 0.03) 0%,
|
||
transparent 50%
|
||
),
|
||
var(--bg-color);
|
||
}
|
||
|
||
#bfc-grid {
|
||
position: absolute;
|
||
inset: 0;
|
||
opacity: 0.22;
|
||
background-image:
|
||
linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
||
background-size: 80px 80px;
|
||
mask-image: radial-gradient(ellipse 70% 60% at 50% 45%, #000 20%, transparent 75%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
#bfc-stage {
|
||
position: absolute;
|
||
inset: 0;
|
||
transform-origin: 50% 48%;
|
||
will-change: transform, filter;
|
||
}
|
||
|
||
#bfc-shot-a,
|
||
#bfc-shot-b {
|
||
position: absolute;
|
||
inset: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#bfc-shot-b {
|
||
visibility: hidden;
|
||
opacity: 0;
|
||
}
|
||
|
||
/* ── Shot A: single product card hero ── */
|
||
#bfc-a-crop {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
#bfc-a-card {
|
||
position: relative;
|
||
width: 680px;
|
||
height: 720px;
|
||
border-radius: 36px;
|
||
background:
|
||
linear-gradient(160deg, rgba(255, 255, 255, 0.06) 0%, transparent 40%),
|
||
linear-gradient(180deg, var(--bfc-panel-2) 0%, var(--bfc-panel) 100%);
|
||
border: 1px solid var(--bfc-line);
|
||
box-shadow:
|
||
0 40px 120px rgba(0, 0, 0, 0.55),
|
||
0 0 0 1px rgba(0, 229, 199, 0.08),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||
overflow: hidden;
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
#bfc-a-card-glow {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 28%;
|
||
width: 420px;
|
||
height: 420px;
|
||
margin-left: -210px;
|
||
margin-top: -210px;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, rgba(0, 229, 199, 0.28) 0%, transparent 68%);
|
||
opacity: 0.85;
|
||
pointer-events: none;
|
||
}
|
||
|
||
#bfc-a-wave {
|
||
position: absolute;
|
||
left: 64px;
|
||
right: 64px;
|
||
top: 120px;
|
||
height: 220px;
|
||
}
|
||
|
||
#bfc-a-wave svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
#bfc-a-wave-path {
|
||
fill: none;
|
||
stroke: var(--accent-color);
|
||
stroke-width: 4;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
#bfc-a-wave-fill {
|
||
fill: var(--accent-color);
|
||
opacity: 0.12;
|
||
}
|
||
|
||
#bfc-a-bars {
|
||
position: absolute;
|
||
left: 80px;
|
||
right: 80px;
|
||
bottom: 160px;
|
||
height: 96px;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 10px;
|
||
}
|
||
|
||
.bfc-bar {
|
||
flex: 1;
|
||
border-radius: 6px 6px 2px 2px;
|
||
background: linear-gradient(180deg, var(--accent-color) 0%, rgba(0, 229, 199, 0.25) 100%);
|
||
transform-origin: 50% 100%;
|
||
height: 40%;
|
||
}
|
||
|
||
#bfc-a-meta {
|
||
position: absolute;
|
||
left: 64px;
|
||
right: 64px;
|
||
bottom: 56px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
#bfc-a-kicker {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: var(--bfc-muted);
|
||
}
|
||
|
||
#bfc-a-pill {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.08em;
|
||
text-transform: uppercase;
|
||
color: #04120f;
|
||
background: var(--accent-color);
|
||
padding: 8px 16px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
/* ── Shot B: split layout, higher info density ── */
|
||
#bfc-b-layout {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: grid;
|
||
grid-template-columns: 1.05fr 0.95fr;
|
||
gap: 48px;
|
||
padding: 100px 110px;
|
||
align-items: center;
|
||
}
|
||
|
||
#bfc-b-left {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 28px;
|
||
transform-origin: 0% 50%;
|
||
}
|
||
|
||
#bfc-b-eyebrow {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.22em;
|
||
text-transform: uppercase;
|
||
color: var(--accent-color);
|
||
}
|
||
|
||
#bfc-b-title {
|
||
font-size: 128px;
|
||
font-weight: 900;
|
||
line-height: 0.92;
|
||
letter-spacing: -0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--bfc-ink);
|
||
}
|
||
|
||
#bfc-b-sub {
|
||
font-size: 28px;
|
||
font-weight: 500;
|
||
color: var(--bfc-muted);
|
||
max-width: 520px;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
#bfc-b-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18px;
|
||
}
|
||
|
||
.bfc-b-card {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 24px;
|
||
padding: 28px 32px;
|
||
border-radius: 20px;
|
||
background: linear-gradient(135deg, var(--bfc-panel-2), var(--bfc-panel));
|
||
border: 1px solid var(--bfc-line);
|
||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.bfc-b-card-label {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: var(--bfc-muted);
|
||
}
|
||
|
||
.bfc-b-card-value {
|
||
font-size: 42px;
|
||
font-weight: 800;
|
||
letter-spacing: -0.03em;
|
||
color: var(--bfc-ink);
|
||
}
|
||
|
||
.bfc-b-card-value.accent {
|
||
color: var(--accent-color);
|
||
}
|
||
|
||
#bfc-b-accent-bar {
|
||
width: 120px;
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
background: var(--accent-color);
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* ── Intro / transient typography ── */
|
||
#bfc-intro {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 72px;
|
||
display: flex;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
z-index: 20;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
}
|
||
|
||
#bfc-intro-label {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.28em;
|
||
text-transform: uppercase;
|
||
color: var(--bfc-muted);
|
||
padding: 10px 18px;
|
||
border: 1px solid var(--bfc-line);
|
||
border-radius: 999px;
|
||
background: rgba(11, 13, 16, 0.55);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
#bfc-hit-type {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
pointer-events: none;
|
||
z-index: 30;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
}
|
||
|
||
#bfc-hit-type-text {
|
||
font-size: 160px;
|
||
font-weight: 900;
|
||
letter-spacing: -0.04em;
|
||
text-transform: uppercase;
|
||
color: var(--bfc-ink);
|
||
text-shadow:
|
||
0 0 40px rgba(0, 229, 199, 0.35),
|
||
0 8px 40px rgba(0, 0, 0, 0.55);
|
||
-webkit-text-stroke: 2px rgba(0, 0, 0, 0.35);
|
||
paint-order: stroke fill;
|
||
}
|
||
|
||
/* ── Freeze frame treatment ── */
|
||
#bfc-freeze {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 25;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
}
|
||
|
||
#bfc-freeze-outline {
|
||
position: absolute;
|
||
inset: 48px;
|
||
border: 3px solid var(--accent-color);
|
||
border-radius: 8px;
|
||
box-shadow:
|
||
0 0 0 1px rgba(0, 229, 199, 0.25),
|
||
inset 0 0 0 1px rgba(0, 229, 199, 0.15),
|
||
0 0 48px rgba(0, 229, 199, 0.2);
|
||
opacity: 0;
|
||
}
|
||
|
||
#bfc-freeze-flash {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: #ffffff;
|
||
opacity: 0;
|
||
mix-blend-mode: screen;
|
||
}
|
||
|
||
#bfc-freeze-contour {
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
linear-gradient(90deg, transparent 0%, rgba(0, 229, 199, 0.08) 48%, transparent 52%),
|
||
radial-gradient(ellipse 40% 55% at 50% 48%, transparent 40%, rgba(0, 229, 199, 0.18) 100%);
|
||
opacity: 0;
|
||
mix-blend-mode: screen;
|
||
}
|
||
|
||
#bfc-freeze-badge {
|
||
position: absolute;
|
||
top: 80px;
|
||
right: 96px;
|
||
font-size: 16px;
|
||
font-weight: 800;
|
||
letter-spacing: 0.2em;
|
||
text-transform: uppercase;
|
||
color: #04120f;
|
||
background: var(--accent-color);
|
||
padding: 10px 18px;
|
||
border-radius: 6px;
|
||
opacity: 0;
|
||
}
|
||
|
||
/* ── Hard-cut connector (directional stretch / blur) ── */
|
||
#bfc-cut {
|
||
position: absolute;
|
||
inset: -8% -20%;
|
||
pointer-events: none;
|
||
z-index: 40;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
background: linear-gradient(
|
||
90deg,
|
||
transparent 0%,
|
||
rgba(0, 229, 199, 0.12) 35%,
|
||
rgba(255, 255, 255, 0.55) 50%,
|
||
rgba(0, 229, 199, 0.12) 65%,
|
||
transparent 100%
|
||
);
|
||
filter: blur(18px);
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
#bfc-vignette {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
z-index: 15;
|
||
background: radial-gradient(
|
||
ellipse 75% 70% at 50% 50%,
|
||
transparent 45%,
|
||
rgba(0, 0, 0, 0.55) 100%
|
||
);
|
||
opacity: 0.85;
|
||
}
|
||
|
||
#bfc-driver {
|
||
position: absolute;
|
||
width: 1px;
|
||
height: 1px;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div
|
||
id="bfc-root"
|
||
data-composition-id="beat-freeze-cut"
|
||
data-start="0"
|
||
data-duration="6"
|
||
data-width="1920"
|
||
data-height="1080"
|
||
>
|
||
<div id="bfc-bg" aria-hidden="true"></div>
|
||
<div id="bfc-grid" aria-hidden="true"></div>
|
||
|
||
<div id="bfc-stage">
|
||
<!-- Shot A -->
|
||
<div id="bfc-shot-a">
|
||
<div id="bfc-a-crop" data-layout-allow-overflow>
|
||
<div id="bfc-a-card" data-layout-allow-overflow>
|
||
<div id="bfc-a-card-glow" aria-hidden="true"></div>
|
||
<div id="bfc-a-wave" aria-hidden="true">
|
||
<svg viewBox="0 0 592 220" preserveAspectRatio="none">
|
||
<path
|
||
id="bfc-a-wave-fill"
|
||
d="M0,160 C40,150 60,90 100,100 C140,110 160,40 200,55 C240,70 260,150 300,140 C340,130 360,30 400,45 C440,60 460,130 500,120 C540,110 560,70 592,80 L592,220 L0,220 Z"
|
||
/>
|
||
<path
|
||
id="bfc-a-wave-path"
|
||
d="M0,160 C40,150 60,90 100,100 C140,110 160,40 200,55 C240,70 260,150 300,140 C340,130 360,30 400,45 C440,60 460,130 500,120 C540,110 560,70 592,80"
|
||
/>
|
||
</svg>
|
||
</div>
|
||
<div id="bfc-a-bars" aria-hidden="true">
|
||
<div class="bfc-bar" id="bfc-bar-0" style="height: 34%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-1" style="height: 58%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-2" style="height: 42%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-3" style="height: 78%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-4" style="height: 52%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-5" style="height: 88%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-6" style="height: 46%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-7" style="height: 64%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-8" style="height: 36%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-9" style="height: 72%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-10" style="height: 48%"></div>
|
||
<div class="bfc-bar" id="bfc-bar-11" style="height: 92%"></div>
|
||
</div>
|
||
<div id="bfc-a-meta">
|
||
<span id="bfc-a-kicker">MUSIC PROMO</span>
|
||
<span id="bfc-a-pill">LIVE</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Shot B -->
|
||
<div id="bfc-shot-b">
|
||
<div id="bfc-b-layout">
|
||
<div id="bfc-b-left">
|
||
<div id="bfc-b-eyebrow">Next shot</div>
|
||
<div id="bfc-b-title">HARD<br />CUT</div>
|
||
<div id="bfc-b-accent-bar" aria-hidden="true"></div>
|
||
<div id="bfc-b-sub">
|
||
Beat-locked freeze, then cut. Built for music-led promos and montages.
|
||
</div>
|
||
</div>
|
||
<div id="bfc-b-right">
|
||
<div class="bfc-b-card" id="bfc-b-card-0">
|
||
<span class="bfc-b-card-label">Ramp</span>
|
||
<span class="bfc-b-card-value accent">1.35→2.2</span>
|
||
</div>
|
||
<div class="bfc-b-card" id="bfc-b-card-1">
|
||
<span class="bfc-b-card-label">Freeze</span>
|
||
<span class="bfc-b-card-value">0.8s</span>
|
||
</div>
|
||
<div class="bfc-b-card" id="bfc-b-card-2">
|
||
<span class="bfc-b-card-label">Cut</span>
|
||
<span class="bfc-b-card-value accent">3.00</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="bfc-vignette" aria-hidden="true"></div>
|
||
|
||
<div id="bfc-intro" aria-hidden="true">
|
||
<span id="bfc-intro-label">ON THE BEAT</span>
|
||
</div>
|
||
|
||
<div id="bfc-hit-type" aria-hidden="true">
|
||
<span id="bfc-hit-type-text">DROP</span>
|
||
</div>
|
||
|
||
<div id="bfc-freeze" aria-hidden="true">
|
||
<div id="bfc-freeze-flash"></div>
|
||
<div id="bfc-freeze-contour"></div>
|
||
<div id="bfc-freeze-outline"></div>
|
||
<div id="bfc-freeze-badge">FREEZE</div>
|
||
</div>
|
||
|
||
<div id="bfc-cut" aria-hidden="true"></div>
|
||
|
||
<div id="bfc-driver" class="clip" data-start="0" data-duration="6" data-track-index="0"></div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
window.__timelines = window.__timelines || {};
|
||
|
||
// ── EDIT ME (not Studio params — see HTML comment) ──────────────
|
||
var CONFIG = {
|
||
primaryLabel: "DROP",
|
||
secondaryLabel: "ON THE BEAT",
|
||
beat1: 0.7,
|
||
beat2: 2.2,
|
||
freezeTime: 2.2,
|
||
intensity: 1.0,
|
||
};
|
||
// ────────────────────────────────────────────────────────────────
|
||
|
||
var DURATION = 6;
|
||
var intensity = Math.max(0.6, Math.min(1.4, Number(CONFIG.intensity) || 1));
|
||
var beat1 = Number(CONFIG.beat1) || 0.7;
|
||
var beat2 = Number(CONFIG.beat2) || 2.2;
|
||
var freezeAt = Number(CONFIG.freezeTime) || beat2;
|
||
var cutAt = 3.0;
|
||
var beat3 = 4.7;
|
||
var holdAt = 5.2;
|
||
|
||
var root = document.getElementById("bfc-root");
|
||
var stage = document.getElementById("bfc-stage");
|
||
var shotA = document.getElementById("bfc-shot-a");
|
||
var shotB = document.getElementById("bfc-shot-b");
|
||
var aCrop = document.getElementById("bfc-a-crop");
|
||
var aCard = document.getElementById("bfc-a-card");
|
||
var intro = document.getElementById("bfc-intro");
|
||
var introLabel = document.getElementById("bfc-intro-label");
|
||
var hitType = document.getElementById("bfc-hit-type");
|
||
var hitText = document.getElementById("bfc-hit-type-text");
|
||
var freeze = document.getElementById("bfc-freeze");
|
||
var freezeOutline = document.getElementById("bfc-freeze-outline");
|
||
var freezeFlash = document.getElementById("bfc-freeze-flash");
|
||
var freezeContour = document.getElementById("bfc-freeze-contour");
|
||
var freezeBadge = document.getElementById("bfc-freeze-badge");
|
||
var cutFx = document.getElementById("bfc-cut");
|
||
var bLeft = document.getElementById("bfc-b-left");
|
||
var bCards = [
|
||
document.getElementById("bfc-b-card-0"),
|
||
document.getElementById("bfc-b-card-1"),
|
||
document.getElementById("bfc-b-card-2"),
|
||
];
|
||
var bars = [];
|
||
for (var bi = 0; bi < 12; bi++) {
|
||
bars.push(document.getElementById("bfc-bar-" + bi));
|
||
}
|
||
|
||
// Apply editable labels
|
||
if (hitText) hitText.textContent = CONFIG.primaryLabel || "DROP";
|
||
if (introLabel) introLabel.textContent = CONFIG.secondaryLabel || "ON THE BEAT";
|
||
|
||
// Accent color from CSS var → freeze/glow that use hardcoded rgba fallbacks
|
||
// stay coherent because stroke/fill already use var(--accent-color).
|
||
|
||
var tl = gsap.timeline({ paused: true });
|
||
|
||
// Initial states (no CSS transform conflicts on animated props)
|
||
gsap.set(stage, { scale: 1, x: 0, y: 0, filter: "blur(0px) brightness(1)" });
|
||
gsap.set(aCrop, { scale: 1, x: 0, y: 0 });
|
||
// Card visible at t=0 so catalog snapshots are never a black frame
|
||
gsap.set(aCard, { scale: 0.9, y: 20, opacity: 1 });
|
||
gsap.set(shotA, { opacity: 1, visibility: "visible" });
|
||
gsap.set(shotB, { opacity: 0, visibility: "hidden" });
|
||
gsap.set(intro, { opacity: 0, visibility: "hidden", y: -12 });
|
||
gsap.set(hitType, { opacity: 0, visibility: "hidden", scale: 1.15 });
|
||
gsap.set(freeze, { opacity: 0, visibility: "hidden" });
|
||
gsap.set([freezeOutline, freezeFlash, freezeContour, freezeBadge], { opacity: 0 });
|
||
gsap.set(cutFx, { opacity: 0, visibility: "hidden", scaleX: 0.4, x: -200 });
|
||
gsap.set(bLeft, { opacity: 0, x: -48 });
|
||
gsap.set(bCards, { opacity: 0, x: 40 });
|
||
gsap.set(bars, { scaleY: 0.55 });
|
||
|
||
// Full duration anchor (seek-safe length)
|
||
tl.to({}, { duration: DURATION, ease: "none" }, 0);
|
||
|
||
// ── 0.00–0.70: intro push-in + secondary type ──
|
||
tl.to(aCard, { scale: 1, y: 0, duration: 0.55, ease: "power3.out" }, 0);
|
||
tl.to(stage, { scale: 1 + 0.04 * intensity, duration: 0.7, ease: "power1.out" }, 0);
|
||
tl.to(bars, { scaleY: 1, duration: 0.5, ease: "power2.out", stagger: 0.02 }, 0.12);
|
||
tl.set(intro, { visibility: "visible" }, 0.12);
|
||
tl.to(intro, { opacity: 1, y: 0, duration: 0.28, ease: "power2.out" }, 0.12);
|
||
tl.to(intro, { opacity: 0, y: -8, duration: 0.18, ease: "power2.in" }, 0.52);
|
||
tl.set(intro, { visibility: "hidden", opacity: 0 }, 0.7);
|
||
|
||
// ── 0.70–1.35: beat 1 — hard reframe ──
|
||
tl.to(
|
||
aCrop,
|
||
{
|
||
scale: 1 + 0.18 * intensity,
|
||
x: -80 * intensity,
|
||
y: 24 * intensity,
|
||
duration: 0.22,
|
||
ease: "power4.out",
|
||
},
|
||
beat1,
|
||
);
|
||
tl.to(
|
||
stage,
|
||
{
|
||
scale: 1.08 * intensity > 1.2 ? 1.2 : 1 + 0.08 * intensity,
|
||
duration: 0.28,
|
||
ease: "expo.out",
|
||
},
|
||
beat1,
|
||
);
|
||
tl.to(
|
||
bars,
|
||
{
|
||
scaleY: function (i) {
|
||
// Deterministic bar pattern (no Math.random)
|
||
var pattern = [0.55, 1.15, 0.7, 1.35, 0.9, 1.4, 0.65, 1.1, 0.5, 1.25, 0.8, 1.45];
|
||
return pattern[i % pattern.length];
|
||
},
|
||
duration: 0.18,
|
||
ease: "power3.out",
|
||
stagger: 0.01,
|
||
},
|
||
beat1,
|
||
);
|
||
|
||
// ── 1.35–2.20: speed ramp into freeze ──
|
||
var rampStart = 1.35;
|
||
var rampDur = Math.max(0.2, freezeAt - rampStart);
|
||
tl.to(
|
||
aCrop,
|
||
{
|
||
scale: 1.28 * (0.9 + 0.1 * intensity),
|
||
x: 28 * intensity,
|
||
y: -12 * intensity,
|
||
duration: rampDur,
|
||
ease: "power3.in",
|
||
},
|
||
rampStart,
|
||
);
|
||
tl.to(
|
||
stage,
|
||
{
|
||
scale: 1.12,
|
||
filter: "blur(0px) brightness(1.08)",
|
||
duration: rampDur,
|
||
ease: "power2.in",
|
||
},
|
||
rampStart,
|
||
);
|
||
tl.to(
|
||
bars,
|
||
{
|
||
scaleY: 1.55,
|
||
duration: rampDur * 0.85,
|
||
ease: "power2.in",
|
||
stagger: { each: 0.012, from: "center" },
|
||
},
|
||
rampStart,
|
||
);
|
||
|
||
// ── 2.20–3.00: freeze hit (snap-on for seek/snapshot stability) ──
|
||
tl.set(freeze, { visibility: "visible", opacity: 1 }, freezeAt);
|
||
tl.set(freezeOutline, { opacity: 1 }, freezeAt);
|
||
tl.set(freezeContour, { opacity: 0.85 }, freezeAt);
|
||
tl.set(freezeBadge, { opacity: 1 }, freezeAt);
|
||
tl.set(hitType, { visibility: "visible", opacity: 1, scale: 1 }, freezeAt);
|
||
// Hold crop/stage where ramp ended
|
||
tl.to(aCrop, { duration: 0.01, ease: "none" }, freezeAt);
|
||
tl.to(
|
||
stage,
|
||
{
|
||
scale: 1.12,
|
||
filter: "blur(0px) brightness(1.15)",
|
||
duration: 0.05,
|
||
ease: "power4.out",
|
||
},
|
||
freezeAt,
|
||
);
|
||
// Single exposure flash (not a strobe sequence)
|
||
tl.fromTo(
|
||
freezeFlash,
|
||
{ opacity: 0.55 * intensity },
|
||
{ opacity: 0, duration: 0.28, ease: "power2.out" },
|
||
freezeAt,
|
||
);
|
||
tl.to(
|
||
hitType,
|
||
{ opacity: 0, scale: 0.96, duration: 0.2, ease: "power2.in" },
|
||
freezeAt + 0.55,
|
||
);
|
||
tl.set(hitType, { visibility: "hidden", opacity: 0 }, freezeAt + 0.78);
|
||
// Soft settle of freeze dressing before cut
|
||
tl.to(
|
||
[freezeOutline, freezeContour, freezeBadge],
|
||
{ opacity: 0.75, duration: 0.35, ease: "sine.out" },
|
||
freezeAt + 0.35,
|
||
);
|
||
|
||
// ── 3.00–3.35: hard cut connector ──
|
||
tl.set(cutFx, { visibility: "visible" }, cutAt);
|
||
tl.fromTo(
|
||
cutFx,
|
||
{ opacity: 0, scaleX: 0.35, x: -280 },
|
||
{
|
||
opacity: 0.95,
|
||
scaleX: 1.6,
|
||
x: 120,
|
||
duration: 0.12,
|
||
ease: "power4.in",
|
||
},
|
||
cutAt,
|
||
);
|
||
tl.to(
|
||
stage,
|
||
{
|
||
filter: "blur(14px) brightness(1.35)",
|
||
scaleX: 1.35,
|
||
scaleY: 0.92,
|
||
duration: 0.12,
|
||
ease: "power3.in",
|
||
},
|
||
cutAt,
|
||
);
|
||
// Switch shots mid-cut — shot B content lands immediately (no black gap)
|
||
var switchAt = cutAt + 0.14;
|
||
tl.set(shotA, { opacity: 0, visibility: "hidden" }, switchAt);
|
||
tl.set(shotB, { opacity: 1, visibility: "visible" }, switchAt);
|
||
tl.set(hitType, { opacity: 0, visibility: "hidden" }, switchAt);
|
||
tl.set(
|
||
[freeze, freezeOutline, freezeFlash, freezeContour, freezeBadge],
|
||
{ opacity: 0, visibility: "hidden" },
|
||
switchAt,
|
||
);
|
||
tl.set(freeze, { visibility: "hidden" }, switchAt);
|
||
// Shot B content visible on switch (offset settles after)
|
||
tl.set(bLeft, { opacity: 1, x: -24 }, switchAt);
|
||
tl.set(bCards, { opacity: 1, x: 24 }, switchAt);
|
||
tl.to(
|
||
stage,
|
||
{
|
||
filter: "blur(0px) brightness(1)",
|
||
scale: 1,
|
||
scaleX: 1,
|
||
scaleY: 1,
|
||
x: 0,
|
||
y: 0,
|
||
duration: 0.18,
|
||
ease: "power3.out",
|
||
},
|
||
switchAt,
|
||
);
|
||
tl.to(cutFx, { opacity: 0, x: 360, duration: 0.16, ease: "power2.out" }, cutAt + 0.16);
|
||
tl.set(cutFx, { visibility: "hidden", opacity: 0 }, cutAt + 0.34);
|
||
|
||
// ── after switch: shot B settle (no scale overlap with recover) ──
|
||
tl.to(bLeft, { x: 0, duration: 0.4, ease: "power3.out" }, switchAt);
|
||
tl.to(
|
||
bCards,
|
||
{
|
||
x: 0,
|
||
duration: 0.38,
|
||
ease: "power3.out",
|
||
stagger: 0.06,
|
||
},
|
||
switchAt + 0.04,
|
||
);
|
||
tl.to(stage, { scale: 1.02, duration: 1.17, ease: "sine.out" }, switchAt + 0.18);
|
||
|
||
// ── 4.70–5.20: final beat punch-in ──
|
||
tl.to(
|
||
stage,
|
||
{
|
||
scale: 1.08 * (0.9 + 0.1 * intensity),
|
||
duration: 0.14,
|
||
ease: "power4.out",
|
||
},
|
||
beat3,
|
||
);
|
||
tl.to(
|
||
bCards[1],
|
||
{
|
||
scale: 1.04,
|
||
duration: 0.12,
|
||
ease: "power3.out",
|
||
},
|
||
beat3,
|
||
);
|
||
tl.to(
|
||
bCards[1],
|
||
{
|
||
scale: 1,
|
||
duration: 0.22,
|
||
ease: "power2.out",
|
||
},
|
||
beat3 + 0.14,
|
||
);
|
||
// Micro freeze feel: brief hold of scale
|
||
tl.to(
|
||
stage,
|
||
{ scale: 1.08 * (0.9 + 0.1 * intensity), duration: 0.2, ease: "none" },
|
||
beat3 + 0.14,
|
||
);
|
||
|
||
// ── 5.20–6.00: hold readable end card (≥0.8s) ──
|
||
tl.to(stage, { scale: 1.03, duration: 0.35, ease: "power2.out" }, holdAt);
|
||
// Ensure transient layers stay dead through end
|
||
tl.set(intro, { opacity: 0, visibility: "hidden" }, holdAt);
|
||
tl.set(hitType, { opacity: 0, visibility: "hidden" }, holdAt);
|
||
tl.set(freeze, { opacity: 0, visibility: "hidden" }, holdAt);
|
||
tl.set(cutFx, { opacity: 0, visibility: "hidden" }, holdAt);
|
||
|
||
window.__timelines["beat-freeze-cut"] = tl;
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|