mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
* fix(docs): load the player from latest, not a pinned minor The catalog pages pinned the player CDN URL to a minor line, and that pin sat one line behind after the last release. Every page kept rendering, on the older build, so nothing surfaced it: the only symptom was that a fix published to npm never appeared on the docs. The generator derived its pin from the player's package.json, which is correct only if every page is regenerated on the release that moves it. That is the step that did not happen, and it has to happen across 175 generated pages plus three hand-written files for the pin to be true. A version carried in step across 178 places will be stale, and stale here is silent. Ask for latest instead and there is nothing to carry. This costs the ability to hold the docs back from a bad player release. Paid deliberately: the pin did not buy that either, it only delayed the good releases too. A test asserts no pinned version comes back, and fails if it stops finding the references at all, so it cannot pass by matching nothing. * refactor(scripts): list tracked files instead of walking the tree The pin guard hand-rolled a recursive directory walk with its own skip list and size cap, which the audit flagged: helpers living in a test file earn no coverage, so their complexity lands straight on the CRAP score. git already knows which files to read, and ignores node_modules and build output for us, so one call replaces the walker and both findings go away.
944 lines
29 KiB
Plaintext
944 lines
29 KiB
Plaintext
---
|
||
title: "Beat Freeze Cut"
|
||
description: "A beat-driven speed ramp, freeze-frame hit, and hard-cut sequence for music-led promos and montages."
|
||
---
|
||
|
||
import { InstallCommand } from "/snippets/install-command.jsx";
|
||
|
||
<iframe
|
||
className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800"
|
||
title="beat-freeze-cut preview"
|
||
loading="lazy"
|
||
srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@latest/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/beat-freeze-cut.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/beat-freeze-cut.png");document.body.appendChild(p)});<\/script></body></html>`}
|
||
/>
|
||
|
||
## Install
|
||
|
||
<InstallCommand command="npx hyperframes add beat-freeze-cut" item="beat-freeze-cut" />
|
||
|
||
That writes one file: `compositions/beat-freeze-cut.html`.
|
||
|
||
## Add it to your video
|
||
|
||
It runs for 6 seconds at 1920×1080. Paste this into your composition:
|
||
|
||
```html index.html
|
||
<div
|
||
data-composition-id="beat-freeze-cut"
|
||
data-composition-src="compositions/beat-freeze-cut.html"
|
||
data-start="0"
|
||
data-duration="6"
|
||
data-track-index="1"
|
||
data-width="1920"
|
||
data-height="1080"
|
||
></div>
|
||
```
|
||
|
||
Move it in time with `data-start`. Put it on a different timeline row with
|
||
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.
|
||
|
||
## Change the colors
|
||
|
||
Set these CSS variables on the block:
|
||
|
||
- `--bg-color` — Background. Defaults to `#0B0D10`.
|
||
- `--accent-color` — Accent. Defaults to `#00E5C7`.
|
||
|
||
## Source
|
||
|
||
<Accordion title={`beat-freeze-cut.html`}>
|
||
|
||
```html
|
||
<!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>
|
||
```
|
||
|
||
</Accordion>
|
||
|
||
{/* hf:generated-footer */}
|
||
|
||
Tagged `transition` `music` `beat-driven` `freeze-frame`.
|
||
|
||
## Related topics
|
||
|
||
- [Browse the complete Catalog](/catalog)
|
||
- [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
|
||
- [Build a richer composition](/go-further)
|