chore: import upstream snapshot with attribution
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
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 12:58:35 +08:00
commit 85453da49f
4031 changed files with 710987 additions and 0 deletions
@@ -0,0 +1,83 @@
<!--
Blend Difference — auto-inverting captions via mix-blend-mode.
Text color inverts per-pixel against whatever is behind it:
white stays white on dark areas, flips to black on light areas.
On color video, white inverts to the complement (blue → orange,
red → cyan, green → magenta).
Setup:
1. The composition root (or a shared ancestor of both the video
and the caption layer) MUST have `isolation: isolate` so the
blend operates against sibling content, not the page background.
2. Add class="blend-difference" to any caption container.
3. Set caption text color to white. The blend mode handles the rest.
Works on any element — divs, spans, SVG text, even images.
Customize:
- --blend-caption-color: base text color (default white)
- Change blend mode via --blend-mode to 'exclusion' for a softer effect
Variants:
- .blend-difference → standard per-pixel inversion
- .blend-difference-soft → exclusion mode, less harsh contrast
- .blend-difference-screen → text glows on dark, fades on light
-->
<style>
.blend-difference {
mix-blend-mode: var(--blend-mode, difference);
color: var(--blend-caption-color, white);
pointer-events: none;
}
.blend-difference-soft {
mix-blend-mode: exclusion;
color: var(--blend-caption-color, white);
pointer-events: none;
}
.blend-difference-screen {
mix-blend-mode: screen;
color: var(--blend-caption-color, white);
pointer-events: none;
}
</style>
<!--
Composition setup example:
<div data-composition-id="root" ... style="isolation: isolate;">
<video id="bg" data-start="0" data-duration="30" data-track-index="0"
src="video.mp4" muted playsinline></video>
<div class="clip blend-difference" data-start="0" data-duration="5" data-track-index="1"
style="position: absolute; inset: 0; z-index: 10;
display: flex; align-items: center; justify-content: center;">
<span style="font-size: 120px; font-weight: 800; text-transform: uppercase;">
YOUR CAPTION
</span>
</div>
</div>
Timeline integration — animate captions normally, blend mode is passive:
tl.from(".caption", {
y: 50, opacity: 0, duration: 0.6, ease: "expo.out"
}, 0.2);
Notes:
- isolation: isolate on the composition root is REQUIRED.
Without it, blend mode composes against the page background
(usually white or black) and you get no inversion.
- Works with any GSAP animation — the blend composites every frame.
- For caption containers with multiple text elements, apply the
class to the shared parent, not each text element individually.
- On pure black backgrounds, white text stays white (difference
of white and black = white). The effect is most visible when
the background has varied luminance or color.
-->
+83
View File
@@ -0,0 +1,83 @@
<!--
Blend Difference — auto-inverting captions via mix-blend-mode.
Text color inverts per-pixel against whatever is behind it:
white stays white on dark areas, flips to black on light areas.
On color video, white inverts to the complement (blue → orange,
red → cyan, green → magenta).
Setup:
1. The composition root (or a shared ancestor of both the video
and the caption layer) MUST have `isolation: isolate` so the
blend operates against sibling content, not the page background.
2. Add class="blend-difference" to any caption container.
3. Set caption text color to white. The blend mode handles the rest.
Works on any element — divs, spans, SVG text, even images.
Customize:
- --blend-caption-color: base text color (default white)
- Change blend mode via --blend-mode to 'exclusion' for a softer effect
Variants:
- .blend-difference → standard per-pixel inversion
- .blend-difference-soft → exclusion mode, less harsh contrast
- .blend-difference-screen → text glows on dark, fades on light
-->
<style>
.blend-difference {
mix-blend-mode: var(--blend-mode, difference);
color: var(--blend-caption-color, white);
pointer-events: none;
}
.blend-difference-soft {
mix-blend-mode: exclusion;
color: var(--blend-caption-color, white);
pointer-events: none;
}
.blend-difference-screen {
mix-blend-mode: screen;
color: var(--blend-caption-color, white);
pointer-events: none;
}
</style>
<!--
Composition setup example:
<div data-composition-id="root" ... style="isolation: isolate;">
<video id="bg" data-start="0" data-duration="30" data-track-index="0"
src="video.mp4" muted playsinline></video>
<div class="clip blend-difference" data-start="0" data-duration="5" data-track-index="1"
style="position: absolute; inset: 0; z-index: 10;
display: flex; align-items: center; justify-content: center;">
<span style="font-size: 120px; font-weight: 800; text-transform: uppercase;">
YOUR CAPTION
</span>
</div>
</div>
Timeline integration — animate captions normally, blend mode is passive:
tl.from(".caption", {
y: 50, opacity: 0, duration: 0.6, ease: "expo.out"
}, 0.2);
Notes:
- isolation: isolate on the composition root is REQUIRED.
Without it, blend mode composes against the page background
(usually white or black) and you get no inversion.
- Works with any GSAP animation — the blend composites every frame.
- For caption containers with multiple text elements, apply the
class to the shared parent, not each text element individually.
- On pure black backgrounds, white text stays white (difference
of white and black = white). The effect is most visible when
the background has varied luminance or color.
-->
@@ -0,0 +1,15 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-blend-difference",
"type": "hyperframes:component",
"title": "Blend Difference",
"description": "Auto-inverting text using mix-blend-mode: difference — flips between white and black per-pixel against the background",
"tags": ["text", "text-effect", "effect", "blend-mode", "contrast", "inversion"],
"files": [
{
"path": "caption-blend-difference.html",
"target": "compositions/components/caption-blend-difference.html",
"type": "hyperframes:snippet"
}
]
}
@@ -0,0 +1,243 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Clip Wipe</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=Poppins:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#clip-wipe {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#wp-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.wp-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#wp-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.wp-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 100px;
visibility: hidden;
}
.wp-word {
font-family: "Poppins", sans-serif;
font-weight: 800;
font-size: 88px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
clip-path: inset(0 100% 0 0);
will-change: clip-path, color, opacity;
}
</style>
</head>
<body>
<div
id="clip-wipe"
data-composition-id="caption-clip-wipe"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="wp-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="wp-overlay"></div>
<div id="wp-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var container = document.getElementById("wp-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "wp-group";
grp.id = "wp-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, 88, "800", "Poppins", 1720);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "wp-word";
span.id = "wp-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = computedSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("wp-w-" + wi);
var isKW = KEYWORDS.has(wi);
tl.to(
wordEl,
{ clipPath: "inset(0 0% 0 0)", duration: 0.3, ease: "power2.out" },
w.start,
);
if (isKW) {
tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1);
}
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.2 }, w.end);
});
var wordEls = grp.querySelectorAll(".wp-word");
tl.to(
wordEls,
{
clipPath: "inset(0 0% 0 100%)",
duration: 0.25,
stagger: 0.04,
ease: "power2.in",
},
g.end - 0.2,
);
tl.set(grp, { visibility: "hidden", opacity: 0 }, g.end);
});
tl.seek(0);
window.__timelines["caption-clip-wipe"] = tl;
})();
</script>
</body>
</html>
+241
View File
@@ -0,0 +1,241 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Clip Wipe</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=Poppins:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#clip-wipe {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#wp-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.wp-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1;
pointer-events: none;
}
#wp-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.wp-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 100px;
visibility: hidden;
}
.wp-word {
font-family: "Poppins", sans-serif;
font-weight: 800;
font-size: 88px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
clip-path: inset(0 100% 0 0);
will-change: clip-path, color, opacity;
}
</style>
</head>
<body>
<div
id="clip-wipe"
data-composition-id="caption-clip-wipe"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="wp-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="wp-overlay"></div>
<div id="wp-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var container = document.getElementById("wp-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "wp-group";
grp.id = "wp-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, 88, "800", "Poppins", 1720);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "wp-word";
span.id = "wp-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = computedSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("wp-w-" + wi);
var isKW = KEYWORDS.has(wi);
tl.to(
wordEl,
{ clipPath: "inset(0 0% 0 0)", duration: 0.3, ease: "power2.out" },
w.start,
);
if (isKW) {
tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1);
}
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.2 }, w.end);
});
var wordEls = grp.querySelectorAll(".wp-word");
tl.to(
wordEls,
{
clipPath: "inset(0 0% 0 100%)",
duration: 0.25,
stagger: 0.04,
ease: "power2.in",
},
g.end - 0.2,
);
tl.set(grp, { visibility: "hidden", opacity: 0 }, g.end);
});
tl.seek(0);
window.__timelines["caption-clip-wipe"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-clip-wipe",
"type": "hyperframes:component",
"title": "Clip Wipe",
"description": "Left-to-right clip-path wipe reveal per word",
"tags": ["captions", "caption-style", "wipe", "clip-path", "reveal"],
"files": [
{
"path": "caption-clip-wipe.html",
"target": "compositions/components/caption-clip-wipe.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-clip-wipe/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-clip-wipe.png"
}
}
@@ -0,0 +1,432 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Editorial Emphasis</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@400&family=Playfair+Display:ital,wght@0,800;1,800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Inter", sans-serif;
}
#editorial-emphasis {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#bg-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
top: 580px;
left: 60px;
width: 1800px;
height: 430px;
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
.caption-block {
position: absolute;
left: 0;
top: 0;
opacity: 0;
transform-origin: 0% 0%;
will-change: transform, opacity;
backface-visibility: hidden;
}
.caption-line {
display: flex;
align-items: baseline;
gap: 14px;
line-height: 1.1;
white-space: nowrap;
}
.caption-line + .caption-line {
margin-top: 8px;
}
.word {
display: inline-block;
color: #f5f0d0;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
will-change: transform, opacity;
backface-visibility: hidden;
}
.word--normal {
font-family: "Inter", sans-serif;
font-size: 86px;
font-weight: 400;
font-style: normal;
}
.word--italic {
font-family: "Inter", sans-serif;
font-size: 86px;
font-weight: 400;
font-style: italic;
}
.word--emphasis {
font-family: "Playfair Display", serif;
font-size: 180px;
font-weight: 800;
font-style: italic;
line-height: 0.9;
color: #f5f0d0;
}
</style>
</head>
<body>
<div
id="editorial-emphasis"
data-composition-id="caption-editorial-emphasis"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bg-video"
muted
playsinline
preload="auto"
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_WIDTH = 1800;
var ENTRY_DUR = 0.1;
var SLIDE_DUR = 0.2;
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
// Generic transcript words
var W = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// Block layout: [wordIndex, type] — n=normal, i=italic, e=emphasis(large)
// Emphasis words: HyperFrames(8), HTML(12), professional(15), code(24), cinema.(27)
var BLOCKS = [
{
line1: [
[0, "n"],
[1, "n"],
[2, "n"],
],
line2: [
[3, "n"],
[4, "n"],
],
},
{
line1: [
[5, "n"],
[6, "n"],
],
line2: [[7, "e"]],
},
{ line1: [[8, "e"]], line2: null },
{
line1: [
[9, "n"],
[10, "n"],
[11, "n"],
],
line2: [[12, "e"]],
},
{
line1: [
[13, "n"],
[14, "n"],
],
line2: [[15, "e"]],
},
{
line1: [
[16, "n"],
[17, "n"],
],
line2: null,
},
{
line1: [[18, "n"]],
line2: [
[19, "n"],
[20, "n"],
],
},
{
line1: [
[21, "n"],
[22, "n"],
],
line2: null,
},
{
line1: [
[23, "i"],
[24, "e"],
],
line2: null,
},
{
line1: [
[25, "n"],
[26, "n"],
],
line2: [[27, "e"]],
},
];
var CLASS_MAP = { n: "word word--normal", i: "word word--italic", e: "word word--emphasis" };
function computeLineSize(linePairs) {
var hasEmphasis = linePairs.some(function (p) {
return p[1] === "e";
});
if (hasEmphasis) {
var lineText = linePairs
.map(function (p) {
return W[p[0]].text;
})
.join(" ");
return fitFontSize(lineText, 180, "800", "Playfair Display", SAFE_WIDTH);
}
var lineText = linePairs
.map(function (p) {
return W[p[0]].text;
})
.join(" ");
return fitFontSize(lineText, 86, "400", "Inter", SAFE_WIDTH);
}
function buildBlocks() {
var stage = document.getElementById("caption-stage");
BLOCKS.forEach(function (block, bi) {
var el = document.createElement("div");
el.className = "caption-block";
el.id = "b" + bi;
var l1Size = computeLineSize(block.line1);
var l1 = document.createElement("div");
l1.className = "caption-line";
l1.id = "b" + bi + "L1";
block.line1.forEach(function (pair, wi) {
var span = document.createElement("span");
span.className = CLASS_MAP[pair[1]];
span.id = "b" + bi + "L1w" + wi;
span.textContent = W[pair[0]].text;
if (pair[1] === "e") {
span.style.fontSize = l1Size + "px";
} else {
span.style.fontSize = l1Size + "px";
}
l1.appendChild(span);
});
el.appendChild(l1);
if (block.line2) {
var l2Size = computeLineSize(block.line2);
var l2 = document.createElement("div");
l2.className = "caption-line";
l2.id = "b" + bi + "L2";
block.line2.forEach(function (pair, wi) {
var span = document.createElement("span");
span.className = CLASS_MAP[pair[1]];
span.id = "b" + bi + "L2w" + wi;
span.textContent = W[pair[0]].text;
if (pair[1] === "e") {
span.style.fontSize = l2Size + "px";
} else {
span.style.fontSize = l2Size + "px";
}
l2.appendChild(span);
});
el.appendChild(l2);
}
stage.appendChild(el);
});
}
buildBlocks();
// Scale blocks that exceed safe width
(function fitBlocks() {
BLOCKS.forEach(function (block, bi) {
var el = document.getElementById("b" + bi);
el.style.opacity = "1";
el.style.position = "relative";
var lines = el.querySelectorAll(".caption-line");
var maxW = 0;
lines.forEach(function (line) {
if (line.scrollWidth > maxW) maxW = line.scrollWidth;
});
if (maxW > SAFE_WIDTH) {
var s = SAFE_WIDTH / maxW;
el.style.transform = "scale(" + s + ")";
}
el.style.opacity = "0";
el.style.position = "";
});
})();
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var allEls = BLOCKS.map(function (_, i) {
return document.getElementById("b" + i);
});
function blockStart(bi) {
return W[BLOCKS[bi].line1[0][0]].start;
}
BLOCKS.forEach(function (block, bi) {
var el = allEls[bi];
var start = blockStart(bi);
var nextStart = bi < BLOCKS.length - 1 ? blockStart(bi + 1) : DURATION;
allEls.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
tl.set(el, { opacity: 1 }, start);
block.line1.forEach(function (pair, wi) {
var wordEl = document.getElementById("b" + bi + "L1w" + wi);
var wordStart = W[pair[0]].start;
tl.set(wordEl, { opacity: 0, scale: 1.12, transformOrigin: "0% 100%" }, start);
tl.to(
wordEl,
{ opacity: 1, scale: 1, duration: ENTRY_DUR, ease: "power2.out" },
wordStart,
);
});
if (block.line2) {
var hasEmphasis = block.line2.some(function (p) {
return p[1] === "e";
});
var l2El = document.getElementById("b" + bi + "L2");
if (hasEmphasis) {
var l2Start = W[block.line2[0][0]].start;
tl.set(l2El, { opacity: 0, x: -1920 }, start);
tl.to(l2El, { opacity: 1, x: 0, duration: SLIDE_DUR, ease: "power2.out" }, l2Start);
} else {
tl.set(l2El, { opacity: 1 }, start);
block.line2.forEach(function (pair, wi) {
var wordEl = document.getElementById("b" + bi + "L2w" + wi);
var wordStart = W[pair[0]].start;
tl.set(wordEl, { opacity: 0, scale: 1.12, transformOrigin: "0% 100%" }, start);
tl.to(
wordEl,
{ opacity: 1, scale: 1, duration: ENTRY_DUR, ease: "power2.out" },
wordStart,
);
});
}
}
tl.set(el, { opacity: 0 }, nextStart);
});
tl.to({}, { duration: DURATION }, 0);
window.__timelines["caption-editorial-emphasis"] = tl;
</script>
</body>
</html>
+430
View File
@@ -0,0 +1,430 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Editorial Emphasis</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@400&family=Playfair+Display:ital,wght@0,800;1,800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000;
font-family: "Inter", sans-serif;
}
#editorial-emphasis {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
#bg-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
top: 580px;
left: 60px;
width: 1800px;
height: 430px;
display: flex;
align-items: flex-start;
justify-content: flex-start;
}
.caption-block {
position: absolute;
left: 0;
top: 0;
opacity: 0;
transform-origin: 0% 0%;
will-change: transform, opacity;
backface-visibility: hidden;
}
.caption-line {
display: flex;
align-items: baseline;
gap: 14px;
line-height: 1.1;
white-space: nowrap;
}
.caption-line + .caption-line {
margin-top: 8px;
}
.word {
display: inline-block;
color: #f5f0d0;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
will-change: transform, opacity;
backface-visibility: hidden;
}
.word--normal {
font-family: "Inter", sans-serif;
font-size: 86px;
font-weight: 400;
font-style: normal;
}
.word--italic {
font-family: "Inter", sans-serif;
font-size: 86px;
font-weight: 400;
font-style: italic;
}
.word--emphasis {
font-family: "Playfair Display", serif;
font-size: 180px;
font-weight: 800;
font-style: italic;
line-height: 0.9;
color: #f5f0d0;
}
</style>
</head>
<body>
<div
id="editorial-emphasis"
data-composition-id="caption-editorial-emphasis"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bg-video"
muted
playsinline
preload="auto"
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_WIDTH = 1800;
var ENTRY_DUR = 0.1;
var SLIDE_DUR = 0.2;
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
// Generic transcript words
var W = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// Block layout: [wordIndex, type] — n=normal, i=italic, e=emphasis(large)
// Emphasis words: HyperFrames(8), HTML(12), professional(15), code(24), cinema.(27)
var BLOCKS = [
{
line1: [
[0, "n"],
[1, "n"],
[2, "n"],
],
line2: [
[3, "n"],
[4, "n"],
],
},
{
line1: [
[5, "n"],
[6, "n"],
],
line2: [[7, "e"]],
},
{ line1: [[8, "e"]], line2: null },
{
line1: [
[9, "n"],
[10, "n"],
[11, "n"],
],
line2: [[12, "e"]],
},
{
line1: [
[13, "n"],
[14, "n"],
],
line2: [[15, "e"]],
},
{
line1: [
[16, "n"],
[17, "n"],
],
line2: null,
},
{
line1: [[18, "n"]],
line2: [
[19, "n"],
[20, "n"],
],
},
{
line1: [
[21, "n"],
[22, "n"],
],
line2: null,
},
{
line1: [
[23, "i"],
[24, "e"],
],
line2: null,
},
{
line1: [
[25, "n"],
[26, "n"],
],
line2: [[27, "e"]],
},
];
var CLASS_MAP = { n: "word word--normal", i: "word word--italic", e: "word word--emphasis" };
function computeLineSize(linePairs) {
var hasEmphasis = linePairs.some(function (p) {
return p[1] === "e";
});
if (hasEmphasis) {
var lineText = linePairs
.map(function (p) {
return W[p[0]].text;
})
.join(" ");
return fitFontSize(lineText, 180, "800", "Playfair Display", SAFE_WIDTH);
}
var lineText = linePairs
.map(function (p) {
return W[p[0]].text;
})
.join(" ");
return fitFontSize(lineText, 86, "400", "Inter", SAFE_WIDTH);
}
function buildBlocks() {
var stage = document.getElementById("caption-stage");
BLOCKS.forEach(function (block, bi) {
var el = document.createElement("div");
el.className = "caption-block";
el.id = "b" + bi;
var l1Size = computeLineSize(block.line1);
var l1 = document.createElement("div");
l1.className = "caption-line";
l1.id = "b" + bi + "L1";
block.line1.forEach(function (pair, wi) {
var span = document.createElement("span");
span.className = CLASS_MAP[pair[1]];
span.id = "b" + bi + "L1w" + wi;
span.textContent = W[pair[0]].text;
if (pair[1] === "e") {
span.style.fontSize = l1Size + "px";
} else {
span.style.fontSize = l1Size + "px";
}
l1.appendChild(span);
});
el.appendChild(l1);
if (block.line2) {
var l2Size = computeLineSize(block.line2);
var l2 = document.createElement("div");
l2.className = "caption-line";
l2.id = "b" + bi + "L2";
block.line2.forEach(function (pair, wi) {
var span = document.createElement("span");
span.className = CLASS_MAP[pair[1]];
span.id = "b" + bi + "L2w" + wi;
span.textContent = W[pair[0]].text;
if (pair[1] === "e") {
span.style.fontSize = l2Size + "px";
} else {
span.style.fontSize = l2Size + "px";
}
l2.appendChild(span);
});
el.appendChild(l2);
}
stage.appendChild(el);
});
}
buildBlocks();
// Scale blocks that exceed safe width
(function fitBlocks() {
BLOCKS.forEach(function (block, bi) {
var el = document.getElementById("b" + bi);
el.style.opacity = "1";
el.style.position = "relative";
var lines = el.querySelectorAll(".caption-line");
var maxW = 0;
lines.forEach(function (line) {
if (line.scrollWidth > maxW) maxW = line.scrollWidth;
});
if (maxW > SAFE_WIDTH) {
var s = SAFE_WIDTH / maxW;
el.style.transform = "scale(" + s + ")";
}
el.style.opacity = "0";
el.style.position = "";
});
})();
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var allEls = BLOCKS.map(function (_, i) {
return document.getElementById("b" + i);
});
function blockStart(bi) {
return W[BLOCKS[bi].line1[0][0]].start;
}
BLOCKS.forEach(function (block, bi) {
var el = allEls[bi];
var start = blockStart(bi);
var nextStart = bi < BLOCKS.length - 1 ? blockStart(bi + 1) : DURATION;
allEls.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
tl.set(el, { opacity: 1 }, start);
block.line1.forEach(function (pair, wi) {
var wordEl = document.getElementById("b" + bi + "L1w" + wi);
var wordStart = W[pair[0]].start;
tl.set(wordEl, { opacity: 0, scale: 1.12, transformOrigin: "0% 100%" }, start);
tl.to(
wordEl,
{ opacity: 1, scale: 1, duration: ENTRY_DUR, ease: "power2.out" },
wordStart,
);
});
if (block.line2) {
var hasEmphasis = block.line2.some(function (p) {
return p[1] === "e";
});
var l2El = document.getElementById("b" + bi + "L2");
if (hasEmphasis) {
var l2Start = W[block.line2[0][0]].start;
tl.set(l2El, { opacity: 0, x: -1920 }, start);
tl.to(l2El, { opacity: 1, x: 0, duration: SLIDE_DUR, ease: "power2.out" }, l2Start);
} else {
tl.set(l2El, { opacity: 1 }, start);
block.line2.forEach(function (pair, wi) {
var wordEl = document.getElementById("b" + bi + "L2w" + wi);
var wordStart = W[pair[0]].start;
tl.set(wordEl, { opacity: 0, scale: 1.12, transformOrigin: "0% 100%" }, start);
tl.to(
wordEl,
{ opacity: 1, scale: 1, duration: ENTRY_DUR, ease: "power2.out" },
wordStart,
);
});
}
}
tl.set(el, { opacity: 0 }, nextStart);
});
tl.to({}, { duration: DURATION }, 0);
window.__timelines["caption-editorial-emphasis"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-editorial-emphasis",
"type": "hyperframes:component",
"title": "Editorial Emphasis",
"description": "Dual-font system with dramatic size contrast for emphasis words",
"tags": ["captions", "caption-style", "editorial", "typography", "emphasis"],
"files": [
{
"path": "caption-editorial-emphasis.html",
"target": "compositions/components/caption-editorial-emphasis.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-editorial-emphasis/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-editorial-emphasis.png"
}
}
@@ -0,0 +1,486 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Emoji Pop</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=Gabarito:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Gabarito", Arial, sans-serif;
}
#emoji-pop {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-emoji {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, calc(-50% - 70px));
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 56px;
line-height: 1;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
}
.caption-word {
display: inline-block;
color: #ffffff;
font-family: "Gabarito", Arial, sans-serif;
font-weight: 900;
font-size: 72px;
line-height: 1.1;
letter-spacing: -0.03em;
text-transform: uppercase;
-webkit-text-stroke: 3px #000000;
paint-order: stroke fill;
}
</style>
</head>
<body>
<div
id="emoji-pop"
data-composition-id="caption-emoji-pop"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var CAPTION_FONT_SIZE = 72;
var MAX_WORDS_PER_GROUP = 3;
var WORD_SPACING = 14;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_FRAMES = 4;
var EXIT_FRAMES = 3;
var FPS = 30;
var ENTRY_DURATION = ENTRY_FRAMES / FPS;
var EXIT_DURATION = EXIT_FRAMES / FPS;
var FONT_WIDTH_SAFETY = 1.14;
var PRIMARY_COLOR = "#FFFFFF";
var ACCENT_COLORS = ["#FF76FF", "#FF0002", "#B2F7FF"];
var KEYWORDS = new Set(["hyperframes", "html", "professional", "code", "cinema"]);
var TRANSITION_WORDS = new Set([
"the",
"a",
"an",
"and",
"or",
"but",
"is",
"are",
"was",
"were",
"to",
"of",
"in",
"on",
"at",
"for",
"with",
"by",
"from",
"as",
"it",
"its",
"this",
"that",
"these",
"those",
"be",
"been",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"must",
"can",
"if",
"then",
"so",
"just",
"not",
"no",
"yes",
"up",
"out",
"about",
]);
// Emoji map for key words
var WORD_EMOJI = {
hyperframes: "🎬",
html: "💻",
professional: "✨",
code: "⌨️",
cinema: "🎥",
video: "📹",
frame: "🖼️",
};
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
var text = String(item.word || item.text || "").trim();
var clean = text.toLowerCase().replace(/[^a-z]/g, "");
return {
text: text,
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
emoji: WORD_EMOJI[clean] || null,
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
function seededRandom(seed) {
var x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
}
function assignWordColors(words, groupIndex) {
return words.map(function (word, index) {
var clean = word.text.toLowerCase().replace(/[^a-z]/g, "");
if (KEYWORDS.has(clean)) {
var ci = Math.floor(seededRandom(groupIndex * 23 + index * 31) * ACCENT_COLORS.length);
return ACCENT_COLORS[ci];
}
if (TRANSITION_WORDS.has(clean)) return PRIMARY_COLOR;
var rand = seededRandom(groupIndex * 13 + index);
if (rand > 0.55) {
var ci2 = Math.floor(rand * ACCENT_COLORS.length) % ACCENT_COLORS.length;
return ACCENT_COLORS[ci2];
}
return PRIMARY_COLOR;
});
}
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function measureWord(word, fontSize) {
measureContext.font = "900 " + fontSize + "px Gabarito";
return measureContext.measureText(word.text.toUpperCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, fontSize) {
return words.reduce(function (total, word, index) {
return total + measureWord(word, fontSize) + (index ? WORD_SPACING : 0);
}, 0);
}
function fitsLine(words) {
return measureLineWidth(words, CAPTION_FONT_SIZE) <= SAFE_ZONE_WIDTH;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
if (testGroup.length > MAX_WORDS_PER_GROUP || !fitsLine(testGroup)) {
if (current.length) groups.push(makeGroup(current, groups.length));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current, groups.length));
current = [];
}
});
if (current.length) groups.push(makeGroup(current, groups.length));
return groups;
}
function makeGroup(words, groupIndex) {
var colors = assignWordColors(words, groupIndex);
var emoji = null;
for (var i = 0; i < words.length; i++) {
if (words[i].emoji) {
emoji = words[i].emoji;
break;
}
}
return {
words: words.slice(),
colors: colors,
emoji: emoji,
start: words[0].start,
end: words[words.length - 1].end,
};
}
function shadowForColor(color) {
var hex = color.replace("#", "");
var r = parseInt(hex.slice(0, 2), 16);
var g = parseInt(hex.slice(2, 4), 16);
var b = parseInt(hex.slice(4, 6), 16);
return (
"0 4px 8px rgba(0,0,0,0.7), 0 0 2px rgba(" +
r +
"," +
g +
"," +
b +
",1), 0 0 8px rgba(" +
r +
"," +
g +
"," +
b +
",0.6)"
);
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupText = group.words
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(
groupText,
CAPTION_FONT_SIZE,
"900",
"Gabarito",
SAFE_ZONE_WIDTH,
);
var groupEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
if (group.emoji) {
var emojiEl = document.createElement("div");
emojiEl.className = "caption-emoji";
emojiEl.textContent = group.emoji;
groupEl.appendChild(emojiEl);
}
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
group.words.forEach(function (word, wordIndex) {
var color = group.colors[wordIndex] || PRIMARY_COLOR;
var wordEl = document.createElement("span");
wordEl.className = "caption-word";
wordEl.textContent = word.text.toUpperCase();
wordEl.style.color = color;
wordEl.style.fontSize = computedSize + "px";
wordEl.style.textShadow = shadowForColor(color);
lineEl.appendChild(wordEl);
});
groupEl.appendChild(lineEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
var exitStart = Math.max(visibleStart, visibleEnd - EXIT_DURATION);
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scaleX: 0.8, scaleY: 1 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scaleX: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
tl.to(
groupEl,
{ scaleX: 0.75, opacity: 0.8, duration: EXIT_DURATION, ease: "power2.in", force3D: true },
exitStart,
);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-emoji-pop"] = tl;
</script>
</body>
</html>
+484
View File
@@ -0,0 +1,484 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Emoji Pop</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=Gabarito:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000000;
font-family: "Gabarito", Arial, sans-serif;
}
#emoji-pop {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000000;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 280px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-emoji {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, calc(-50% - 70px));
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
font-size: 56px;
line-height: 1;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
}
.caption-word {
display: inline-block;
color: #ffffff;
font-family: "Gabarito", Arial, sans-serif;
font-weight: 900;
font-size: 72px;
line-height: 1.1;
letter-spacing: -0.03em;
text-transform: uppercase;
-webkit-text-stroke: 3px #000000;
paint-order: stroke fill;
}
</style>
</head>
<body>
<div
id="emoji-pop"
data-composition-id="caption-emoji-pop"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var CAPTION_FONT_SIZE = 72;
var MAX_WORDS_PER_GROUP = 3;
var WORD_SPACING = 14;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_FRAMES = 4;
var EXIT_FRAMES = 3;
var FPS = 30;
var ENTRY_DURATION = ENTRY_FRAMES / FPS;
var EXIT_DURATION = EXIT_FRAMES / FPS;
var FONT_WIDTH_SAFETY = 1.14;
var PRIMARY_COLOR = "#FFFFFF";
var ACCENT_COLORS = ["#FF76FF", "#FF0002", "#B2F7FF"];
var KEYWORDS = new Set(["hyperframes", "html", "professional", "code", "cinema"]);
var TRANSITION_WORDS = new Set([
"the",
"a",
"an",
"and",
"or",
"but",
"is",
"are",
"was",
"were",
"to",
"of",
"in",
"on",
"at",
"for",
"with",
"by",
"from",
"as",
"it",
"its",
"this",
"that",
"these",
"those",
"be",
"been",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"must",
"can",
"if",
"then",
"so",
"just",
"not",
"no",
"yes",
"up",
"out",
"about",
]);
// Emoji map for key words
var WORD_EMOJI = {
hyperframes: "🎬",
html: "💻",
professional: "✨",
code: "⌨️",
cinema: "🎥",
video: "📹",
frame: "🖼️",
};
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
var text = String(item.word || item.text || "").trim();
var clean = text.toLowerCase().replace(/[^a-z]/g, "");
return {
text: text,
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
emoji: WORD_EMOJI[clean] || null,
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
function seededRandom(seed) {
var x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
}
function assignWordColors(words, groupIndex) {
return words.map(function (word, index) {
var clean = word.text.toLowerCase().replace(/[^a-z]/g, "");
if (KEYWORDS.has(clean)) {
var ci = Math.floor(seededRandom(groupIndex * 23 + index * 31) * ACCENT_COLORS.length);
return ACCENT_COLORS[ci];
}
if (TRANSITION_WORDS.has(clean)) return PRIMARY_COLOR;
var rand = seededRandom(groupIndex * 13 + index);
if (rand > 0.55) {
var ci2 = Math.floor(rand * ACCENT_COLORS.length) % ACCENT_COLORS.length;
return ACCENT_COLORS[ci2];
}
return PRIMARY_COLOR;
});
}
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function measureWord(word, fontSize) {
measureContext.font = "900 " + fontSize + "px Gabarito";
return measureContext.measureText(word.text.toUpperCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, fontSize) {
return words.reduce(function (total, word, index) {
return total + measureWord(word, fontSize) + (index ? WORD_SPACING : 0);
}, 0);
}
function fitsLine(words) {
return measureLineWidth(words, CAPTION_FONT_SIZE) <= SAFE_ZONE_WIDTH;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
if (testGroup.length > MAX_WORDS_PER_GROUP || !fitsLine(testGroup)) {
if (current.length) groups.push(makeGroup(current, groups.length));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current, groups.length));
current = [];
}
});
if (current.length) groups.push(makeGroup(current, groups.length));
return groups;
}
function makeGroup(words, groupIndex) {
var colors = assignWordColors(words, groupIndex);
var emoji = null;
for (var i = 0; i < words.length; i++) {
if (words[i].emoji) {
emoji = words[i].emoji;
break;
}
}
return {
words: words.slice(),
colors: colors,
emoji: emoji,
start: words[0].start,
end: words[words.length - 1].end,
};
}
function shadowForColor(color) {
var hex = color.replace("#", "");
var r = parseInt(hex.slice(0, 2), 16);
var g = parseInt(hex.slice(2, 4), 16);
var b = parseInt(hex.slice(4, 6), 16);
return (
"0 4px 8px rgba(0,0,0,0.7), 0 0 2px rgba(" +
r +
"," +
g +
"," +
b +
",1), 0 0 8px rgba(" +
r +
"," +
g +
"," +
b +
",0.6)"
);
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupText = group.words
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(
groupText,
CAPTION_FONT_SIZE,
"900",
"Gabarito",
SAFE_ZONE_WIDTH,
);
var groupEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
if (group.emoji) {
var emojiEl = document.createElement("div");
emojiEl.className = "caption-emoji";
emojiEl.textContent = group.emoji;
groupEl.appendChild(emojiEl);
}
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
group.words.forEach(function (word, wordIndex) {
var color = group.colors[wordIndex] || PRIMARY_COLOR;
var wordEl = document.createElement("span");
wordEl.className = "caption-word";
wordEl.textContent = word.text.toUpperCase();
wordEl.style.color = color;
wordEl.style.fontSize = computedSize + "px";
wordEl.style.textShadow = shadowForColor(color);
lineEl.appendChild(wordEl);
});
groupEl.appendChild(lineEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
var exitStart = Math.max(visibleStart, visibleEnd - EXIT_DURATION);
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scaleX: 0.8, scaleY: 1 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scaleX: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
tl.to(
groupEl,
{ scaleX: 0.75, opacity: 0.8, duration: EXIT_DURATION, ease: "power2.in", force3D: true },
exitStart,
);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-emoji-pop"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-emoji-pop",
"type": "hyperframes:component",
"title": "Emoji Pop",
"description": "Emoji integration with stroked text and horizontal squeeze entrance",
"tags": ["captions", "caption-style", "emoji", "social"],
"files": [
{
"path": "caption-emoji-pop.html",
"target": "compositions/components/caption-emoji-pop.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-emoji-pop/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-emoji-pop.png"
}
}
@@ -0,0 +1,346 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Glitch RGB</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=Space+Grotesk:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#caption-glitch-rgb {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#gl-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.gl-overlay {
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.08) 0%,
rgba(0, 0, 0, 0.16) 54%,
rgba(0, 0, 0, 0.5) 100%
);
pointer-events: none;
}
.gl-scanlines {
position: absolute;
inset: 0;
z-index: 5;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 3px,
rgba(0, 0, 0, 0.16) 3px,
rgba(0, 0, 0, 0.16) 4px
);
opacity: 0.55;
}
#gl-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.gl-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 14px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.gl-word {
font-family: "Space Grotesk", sans-serif;
font-weight: 700;
font-size: 88px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.03em;
line-height: 0.96;
text-shadow: 0 5px 18px rgba(0, 0, 0, 0.52);
will-change: transform, text-shadow;
}
</style>
</head>
<body>
<div
id="caption-glitch-rgb"
data-composition-id="caption-glitch-rgb"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="gl-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="gl-overlay"></div>
<div class="gl-scanlines"></div>
<div id="gl-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function buildGroups(words) {
var groups = [];
var start = 0;
var chars = 0;
for (var i = 0; i < words.length; i++) {
chars += words[i].text.length + 1;
var endsSentence = /[.!?]$/.test(words[i].text);
var nextGap = i + 1 < words.length ? words[i + 1].start - words[i].end : 0;
var full = i - start >= 3 || chars >= 21;
if (endsSentence || nextGap > 0.28 || full || i === words.length - 1) {
groups.push([start, i]);
start = i + 1;
chars = 0;
}
}
return groups;
}
function glitchPlan(word, nextWord) {
var nextStart = nextWord ? nextWord.start : word.end + 0.3;
var available = Math.min(word.end, nextStart) - word.start - 0.02;
var total = Math.min(0.24, Math.max(0.08, available));
return {
d1: total * 0.35,
d2: total * 0.65,
};
}
function shouldAftershock(word) {
return /\d|\$|%/.test(word.text) || word.text.replace(/[^\w]/g, "").length >= 7;
}
var RAW_GROUPS = buildGroups(TRANSCRIPT);
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0];
var we = pair[1];
var nextStart =
gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : 8.0;
var end = Math.min(TRANSCRIPT[we].end + 0.24, nextStart - 0.04);
return { wordStart: ws, wordEnd: we, start: TRANSCRIPT[ws].start, end: end };
});
var BASE_FONT = 88;
var container = document.getElementById("gl-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = TRANSCRIPT.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "gl-group";
grp.id = "gl-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, BASE_FONT, "700", "Space Grotesk", 1720);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "gl-word";
span.id = "gl-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = computedSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0 },
{ opacity: 1, duration: 0.12, ease: "power2.out" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("gl-w-" + wi);
var plan = glitchPlan(w, TRANSCRIPT[wi + 1]);
var rng = mulberry32(wi * 777 + 13);
var travel = -(8 + rng() * 8);
var shadowMag = 5 + rng() * 7;
var sm = shadowMag.toFixed(1);
var nextWord = TRANSCRIPT[wi + 1];
var nextStart = nextWord ? nextWord.start : g.end;
var aftershockEnd = Math.min(g.end - 0.08, nextStart - 0.04);
tl.to(
wordEl,
{
x: travel,
textShadow:
sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: plan.d1,
ease: "none",
},
w.start,
);
tl.to(
wordEl,
{
x: 0,
textShadow: "0 5px 18px rgba(0,0,0,0.52)",
duration: plan.d2,
ease: "power3.out",
},
w.start + plan.d1,
);
if (shouldAftershock(w) && aftershockEnd - w.end > 0.22) {
var pulseStart = w.end + 0.08;
var pulseIndex = 0;
while (pulseStart + 0.1 < aftershockEnd) {
var pulseTravel = -(3 + rng() * 5);
var pulseShadow = (3 + rng() * 5).toFixed(1);
tl.to(
wordEl,
{
x: pulseTravel,
textShadow:
pulseShadow +
"px 0 #ff003c, -" +
pulseShadow +
"px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: 0.045,
ease: "none",
},
pulseStart,
);
tl.to(
wordEl,
{
x: 0,
textShadow: "0 5px 18px rgba(0,0,0,0.52)",
duration: 0.075,
ease: "power3.out",
},
pulseStart + 0.045,
);
pulseIndex++;
pulseStart += pulseIndex % 2 === 0 ? 0.18 : 0.24;
}
}
});
tl.to(grp, { opacity: 0, duration: 0.1, ease: "power2.in" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-glitch-rgb"] = tl;
})();
</script>
</body>
</html>
+344
View File
@@ -0,0 +1,344 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Glitch RGB</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=Space+Grotesk:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#caption-glitch-rgb {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#gl-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.gl-overlay {
position: absolute;
inset: 0;
z-index: 1;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.08) 0%,
rgba(0, 0, 0, 0.16) 54%,
rgba(0, 0, 0, 0.5) 100%
);
pointer-events: none;
}
.gl-scanlines {
position: absolute;
inset: 0;
z-index: 5;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
transparent 0px,
transparent 3px,
rgba(0, 0, 0, 0.16) 3px,
rgba(0, 0, 0, 0.16) 4px
);
opacity: 0.55;
}
#gl-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.gl-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 14px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.gl-word {
font-family: "Space Grotesk", sans-serif;
font-weight: 700;
font-size: 88px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.03em;
line-height: 0.96;
text-shadow: 0 5px 18px rgba(0, 0, 0, 0.52);
will-change: transform, text-shadow;
}
</style>
</head>
<body>
<div
id="caption-glitch-rgb"
data-composition-id="caption-glitch-rgb"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="gl-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="gl-overlay"></div>
<div class="gl-scanlines"></div>
<div id="gl-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
function buildGroups(words) {
var groups = [];
var start = 0;
var chars = 0;
for (var i = 0; i < words.length; i++) {
chars += words[i].text.length + 1;
var endsSentence = /[.!?]$/.test(words[i].text);
var nextGap = i + 1 < words.length ? words[i + 1].start - words[i].end : 0;
var full = i - start >= 3 || chars >= 21;
if (endsSentence || nextGap > 0.28 || full || i === words.length - 1) {
groups.push([start, i]);
start = i + 1;
chars = 0;
}
}
return groups;
}
function glitchPlan(word, nextWord) {
var nextStart = nextWord ? nextWord.start : word.end + 0.3;
var available = Math.min(word.end, nextStart) - word.start - 0.02;
var total = Math.min(0.24, Math.max(0.08, available));
return {
d1: total * 0.35,
d2: total * 0.65,
};
}
function shouldAftershock(word) {
return /\d|\$|%/.test(word.text) || word.text.replace(/[^\w]/g, "").length >= 7;
}
var RAW_GROUPS = buildGroups(TRANSCRIPT);
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0];
var we = pair[1];
var nextStart =
gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : 8.0;
var end = Math.min(TRANSCRIPT[we].end + 0.24, nextStart - 0.04);
return { wordStart: ws, wordEnd: we, start: TRANSCRIPT[ws].start, end: end };
});
var BASE_FONT = 88;
var container = document.getElementById("gl-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = TRANSCRIPT.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "gl-group";
grp.id = "gl-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, BASE_FONT, "700", "Space Grotesk", 1720);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "gl-word";
span.id = "gl-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = computedSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0 },
{ opacity: 1, duration: 0.12, ease: "power2.out" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("gl-w-" + wi);
var plan = glitchPlan(w, TRANSCRIPT[wi + 1]);
var rng = mulberry32(wi * 777 + 13);
var travel = -(8 + rng() * 8);
var shadowMag = 5 + rng() * 7;
var sm = shadowMag.toFixed(1);
var nextWord = TRANSCRIPT[wi + 1];
var nextStart = nextWord ? nextWord.start : g.end;
var aftershockEnd = Math.min(g.end - 0.08, nextStart - 0.04);
tl.to(
wordEl,
{
x: travel,
textShadow:
sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: plan.d1,
ease: "none",
},
w.start,
);
tl.to(
wordEl,
{
x: 0,
textShadow: "0 5px 18px rgba(0,0,0,0.52)",
duration: plan.d2,
ease: "power3.out",
},
w.start + plan.d1,
);
if (shouldAftershock(w) && aftershockEnd - w.end > 0.22) {
var pulseStart = w.end + 0.08;
var pulseIndex = 0;
while (pulseStart + 0.1 < aftershockEnd) {
var pulseTravel = -(3 + rng() * 5);
var pulseShadow = (3 + rng() * 5).toFixed(1);
tl.to(
wordEl,
{
x: pulseTravel,
textShadow:
pulseShadow +
"px 0 #ff003c, -" +
pulseShadow +
"px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: 0.045,
ease: "none",
},
pulseStart,
);
tl.to(
wordEl,
{
x: 0,
textShadow: "0 5px 18px rgba(0,0,0,0.52)",
duration: 0.075,
ease: "power3.out",
},
pulseStart + 0.045,
);
pulseIndex++;
pulseStart += pulseIndex % 2 === 0 ? 0.18 : 0.24;
}
}
});
tl.to(grp, { opacity: 0, duration: 0.1, ease: "power2.in" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-glitch-rgb"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-glitch-rgb",
"type": "hyperframes:component",
"title": "Glitch RGB",
"description": "RGB chromatic aberration with CRT scanline overlay",
"tags": ["captions", "caption-style", "glitch", "cyber", "tech"],
"files": [
{
"path": "caption-glitch-rgb.html",
"target": "compositions/components/caption-glitch-rgb.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-glitch-rgb/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-glitch-rgb.png"
}
}
@@ -0,0 +1,228 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Gradient Fill</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=Montserrat:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#gradient-fill {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#gr-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.gr-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.gr-word {
font-family: "Montserrat", sans-serif;
font-weight: 900;
font-size: 100px;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1.15;
padding-bottom: 0.05em;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
background-size: 350% 100%;
background-position: 100% 0;
}
</style>
</head>
<body>
<div
id="gradient-fill"
data-composition-id="caption-gradient-fill"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="gr-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div id="gr-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 },
{ wordStart: 13, wordEnd: 14, start: 3.55, end: 3.9 },
{ wordStart: 15, wordEnd: 16, start: 3.95, end: 4.75 },
{ wordStart: 17, wordEnd: 18, start: 4.9, end: 5.4 },
{ wordStart: 19, wordEnd: 22, start: 5.5, end: 6.2 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 },
];
var SIRI_GRAD =
"linear-gradient(90deg, " +
"#fe9f1b 0%, #f76e49 10%, #ff2063 20%, " +
"#fd56cb 30%, #ef7aff 40%, #fe9f1b 50%, " +
"white 50.5%, white 100%)";
var container = document.getElementById("gr-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text;
})
.join(" ");
var fontSize = fitFontSize(groupText, 100, "900", "'Montserrat', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "gr-group";
grp.id = "gr-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "gr-word";
span.id = "gr-w-" + wi;
span.textContent = w.text;
span.style.fontSize = fontSize + "px";
span.style.background = SIRI_GRAD;
span.style.backgroundSize = "350% 100%";
span.style.backgroundPosition = "100% 0";
span.style.webkitBackgroundClip = "text";
span.style.backgroundClip = "text";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 1 }, g.start);
var lastWordEnd = groupWords[groupWords.length - 1].end;
var nextStart = gi < GROUPS.length - 1 ? GROUPS[gi + 1].start : lastWordEnd + 0.5;
var gap = nextStart - lastWordEnd;
var fadeDur = Math.min(0.25, gap * 0.8);
var fadeStart = nextStart - fadeDur;
var groupEnd = nextStart;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var el = document.getElementById("gr-w-" + wi);
var dur = w.end - w.start;
tl.set(el, { backgroundPosition: "100% 0", scale: 1 }, g.start);
tl.set(el, { scale: 1.04 }, w.start);
tl.fromTo(
el,
{ backgroundPosition: "45% 0" },
{ backgroundPosition: "0% 0", duration: dur, ease: "none" },
w.start,
);
tl.set(el, { backgroundPosition: "100% 0" }, w.end);
tl.to(el, { scale: 1, duration: 0.15, ease: "power2.out" }, w.end);
});
tl.to(grp, { opacity: 0, duration: fadeDur }, fadeStart);
tl.set(grp, { visibility: "hidden" }, groupEnd);
});
window.__timelines["caption-gradient-fill"] = tl;
})();
</script>
</body>
</html>
+226
View File
@@ -0,0 +1,226 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Gradient Fill</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=Montserrat:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#gradient-fill {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#gr-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.gr-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.gr-word {
font-family: "Montserrat", sans-serif;
font-weight: 900;
font-size: 100px;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1.15;
padding-bottom: 0.05em;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
background-size: 350% 100%;
background-position: 100% 0;
}
</style>
</head>
<body>
<div
id="gradient-fill"
data-composition-id="caption-gradient-fill"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="gr-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div id="gr-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 },
{ wordStart: 13, wordEnd: 14, start: 3.55, end: 3.9 },
{ wordStart: 15, wordEnd: 16, start: 3.95, end: 4.75 },
{ wordStart: 17, wordEnd: 18, start: 4.9, end: 5.4 },
{ wordStart: 19, wordEnd: 22, start: 5.5, end: 6.2 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 },
];
var SIRI_GRAD =
"linear-gradient(90deg, " +
"#fe9f1b 0%, #f76e49 10%, #ff2063 20%, " +
"#fd56cb 30%, #ef7aff 40%, #fe9f1b 50%, " +
"white 50.5%, white 100%)";
var container = document.getElementById("gr-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text;
})
.join(" ");
var fontSize = fitFontSize(groupText, 100, "900", "'Montserrat', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "gr-group";
grp.id = "gr-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "gr-word";
span.id = "gr-w-" + wi;
span.textContent = w.text;
span.style.fontSize = fontSize + "px";
span.style.background = SIRI_GRAD;
span.style.backgroundSize = "350% 100%";
span.style.backgroundPosition = "100% 0";
span.style.webkitBackgroundClip = "text";
span.style.backgroundClip = "text";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 1 }, g.start);
var lastWordEnd = groupWords[groupWords.length - 1].end;
var nextStart = gi < GROUPS.length - 1 ? GROUPS[gi + 1].start : lastWordEnd + 0.5;
var gap = nextStart - lastWordEnd;
var fadeDur = Math.min(0.25, gap * 0.8);
var fadeStart = nextStart - fadeDur;
var groupEnd = nextStart;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var el = document.getElementById("gr-w-" + wi);
var dur = w.end - w.start;
tl.set(el, { backgroundPosition: "100% 0", scale: 1 }, g.start);
tl.set(el, { scale: 1.04 }, w.start);
tl.fromTo(
el,
{ backgroundPosition: "45% 0" },
{ backgroundPosition: "0% 0", duration: dur, ease: "none" },
w.start,
);
tl.set(el, { backgroundPosition: "100% 0" }, w.end);
tl.to(el, { scale: 1, duration: 0.15, ease: "power2.out" }, w.end);
});
tl.to(grp, { opacity: 0, duration: fadeDur }, fadeStart);
tl.set(grp, { visibility: "hidden" }, groupEnd);
});
window.__timelines["caption-gradient-fill"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-gradient-fill",
"type": "hyperframes:component",
"title": "Gradient Fill",
"description": "Gradient-clipped text with elastic bounce entrance",
"tags": ["captions", "caption-style", "gradient", "colorful"],
"files": [
{
"path": "caption-gradient-fill.html",
"target": "compositions/components/caption-gradient-fill.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-gradient-fill/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-gradient-fill.png"
}
}
@@ -0,0 +1,261 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Highlight</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=Montserrat:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#highlight {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#hl-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.hl-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#hl-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.hl-group {
position: absolute;
bottom: 140px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 8px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.hl-word {
font-family: "Montserrat", sans-serif;
font-weight: 800;
font-size: 80px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.02em;
line-height: 1;
position: relative;
padding: 6px 12px 8px;
text-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
transform-origin: 50% 58%;
will-change: transform, filter;
}
.hl-word-bg {
position: absolute;
inset: 0;
background: linear-gradient(135deg, #ff1745 0%, #df1238 100%);
border-radius: 10px;
box-shadow: 0 12px 30px rgba(229, 20, 58, 0.32);
opacity: 0;
transform: scaleX(0);
transform-origin: 0% 50%;
z-index: -1;
}
.hl-word-text {
position: relative;
z-index: 1;
}
</style>
</head>
<body>
<div
id="highlight"
data-composition-id="caption-highlight"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<div class="hl-overlay"></div>
<div id="hl-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var container = document.getElementById("hl-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "hl-group";
grp.id = "hl-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, 80, "800", "Montserrat", 1620);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.createElement("span");
wordEl.className = "hl-word";
wordEl.id = "hl-w-" + wi;
wordEl.style.fontSize = computedSize + "px";
var bgEl = document.createElement("span");
bgEl.className = "hl-word-bg";
bgEl.id = "hl-bg-" + wi;
var textEl = document.createElement("span");
textEl.className = "hl-word-text";
textEl.textContent = w.text.toUpperCase();
wordEl.appendChild(bgEl);
wordEl.appendChild(textEl);
grp.appendChild(wordEl);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0 },
{ opacity: 1, duration: 0.12, ease: "power2.out" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var bgEl = document.getElementById("hl-bg-" + wi);
var wordEl = document.getElementById("hl-w-" + wi);
tl.to(bgEl, { opacity: 1, scaleX: 1, duration: 0.15, ease: "power2.out" }, w.start);
tl.to(
wordEl,
{ filter: "brightness(1.05)", duration: 0.08, ease: "power2.out" },
w.start,
);
tl.to(
wordEl,
{ filter: "brightness(1)", duration: 0.16, ease: "power2.out" },
w.start + 0.08,
);
tl.to(bgEl, { opacity: 0, scaleX: 1.02, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(bgEl, { scaleX: 0 }, w.end + 0.1);
});
tl.to(grp, { opacity: 0, duration: 0.1, ease: "power2.in" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-highlight"] = tl;
})();
</script>
</body>
</html>
+259
View File
@@ -0,0 +1,259 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Highlight</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=Montserrat:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#highlight {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#hl-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.hl-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 1;
pointer-events: none;
}
#hl-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.hl-group {
position: absolute;
bottom: 140px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 8px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.hl-word {
font-family: "Montserrat", sans-serif;
font-weight: 800;
font-size: 80px;
text-transform: uppercase;
color: #ffffff;
display: inline-block;
letter-spacing: 0.02em;
line-height: 1;
position: relative;
padding: 6px 12px 8px;
text-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
transform-origin: 50% 58%;
will-change: transform, filter;
}
.hl-word-bg {
position: absolute;
inset: 0;
background: linear-gradient(135deg, #ff1745 0%, #df1238 100%);
border-radius: 10px;
box-shadow: 0 12px 30px rgba(229, 20, 58, 0.32);
opacity: 0;
transform: scaleX(0);
transform-origin: 0% 50%;
z-index: -1;
}
.hl-word-text {
position: relative;
z-index: 1;
}
</style>
</head>
<body>
<div
id="highlight"
data-composition-id="caption-highlight"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<div class="hl-overlay"></div>
<div id="hl-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var container = document.getElementById("hl-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div");
grp.className = "hl-group";
grp.id = "hl-grp-" + gi;
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var computedSize = fitFontSize(groupText, 80, "800", "Montserrat", 1620);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.createElement("span");
wordEl.className = "hl-word";
wordEl.id = "hl-w-" + wi;
wordEl.style.fontSize = computedSize + "px";
var bgEl = document.createElement("span");
bgEl.className = "hl-word-bg";
bgEl.id = "hl-bg-" + wi;
var textEl = document.createElement("span");
textEl.className = "hl-word-text";
textEl.textContent = w.text.toUpperCase();
wordEl.appendChild(bgEl);
wordEl.appendChild(textEl);
grp.appendChild(wordEl);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0 },
{ opacity: 1, duration: 0.12, ease: "power2.out" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var bgEl = document.getElementById("hl-bg-" + wi);
var wordEl = document.getElementById("hl-w-" + wi);
tl.to(bgEl, { opacity: 1, scaleX: 1, duration: 0.15, ease: "power2.out" }, w.start);
tl.to(
wordEl,
{ filter: "brightness(1.05)", duration: 0.08, ease: "power2.out" },
w.start,
);
tl.to(
wordEl,
{ filter: "brightness(1)", duration: 0.16, ease: "power2.out" },
w.start + 0.08,
);
tl.to(bgEl, { opacity: 0, scaleX: 1.02, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(bgEl, { scaleX: 0 }, w.end + 0.1);
});
tl.to(grp, { opacity: 0, duration: 0.1, ease: "power2.in" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-highlight"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-highlight",
"type": "hyperframes:component",
"title": "Highlight",
"description": "Red background sweep behind each active word, TikTok-style",
"tags": ["captions", "caption-style", "highlight", "social", "tiktok"],
"files": [
{
"path": "caption-highlight.html",
"target": "compositions/components/caption-highlight.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-highlight/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-highlight.png"
}
}
@@ -0,0 +1,211 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Kinetic Slam</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=Anton&display=swap" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#kinetic-slam {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#kt-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.kt-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#kt-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.kt-word {
font-family: "Anton", sans-serif;
font-weight: 400;
text-transform: uppercase;
color: #ffffff;
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 50%;
letter-spacing: 0.04em;
line-height: 1;
opacity: 0;
visibility: hidden;
}
</style>
</head>
<body>
<div
id="kinetic-slam"
data-composition-id="caption-kinetic-slam"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="kt-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="kt-overlay"></div>
<div id="kt-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var container = document.getElementById("kt-container");
var tl = gsap.timeline({ paused: true });
WORDS.forEach(function (w, wi) {
var el = document.createElement("div");
el.className = "kt-word";
el.id = "kt-w-" + wi;
el.textContent = w.text.toUpperCase();
el.style.fontSize =
fitFontSize(w.text.toUpperCase(), 220, "400", "'Anton', sans-serif", 1720) + "px";
if (KEYWORDS.has(wi)) el.style.color = "#FFD700";
container.appendChild(el);
gsap.set(el, { yPercent: -50 });
var entranceMode = wi % 4;
tl.set(el, { visibility: "visible" }, w.start);
if (entranceMode === 0) {
tl.fromTo(
el,
{ y: -120, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.22, ease: "back.out(1.7)" },
w.start,
);
} else if (entranceMode === 1) {
tl.fromTo(
el,
{ x: -300, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.2, ease: "expo.out" },
w.start,
);
} else if (entranceMode === 2) {
tl.fromTo(
el,
{ x: 300, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.2, ease: "expo.out" },
w.start,
);
} else {
tl.fromTo(
el,
{ scale: 0.4, opacity: 0 },
{ scale: 1, opacity: 1, duration: 0.24, ease: "back.out(2.2)" },
w.start,
);
}
tl.to(el, { opacity: 0, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(el, { opacity: 0, visibility: "hidden" }, w.end + 0.1);
});
tl.seek(0);
window.__timelines["caption-kinetic-slam"] = tl;
})();
</script>
</body>
</html>
+209
View File
@@ -0,0 +1,209 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Kinetic Slam</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=Anton&display=swap" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#kinetic-slam {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#kt-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.kt-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1;
pointer-events: none;
}
#kt-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.kt-word {
font-family: "Anton", sans-serif;
font-weight: 400;
text-transform: uppercase;
color: #ffffff;
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 50%;
letter-spacing: 0.04em;
line-height: 1;
opacity: 0;
visibility: hidden;
}
</style>
</head>
<body>
<div
id="kinetic-slam"
data-composition-id="caption-kinetic-slam"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="kt-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="kt-overlay"></div>
<div id="kt-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var container = document.getElementById("kt-container");
var tl = gsap.timeline({ paused: true });
WORDS.forEach(function (w, wi) {
var el = document.createElement("div");
el.className = "kt-word";
el.id = "kt-w-" + wi;
el.textContent = w.text.toUpperCase();
el.style.fontSize =
fitFontSize(w.text.toUpperCase(), 220, "400", "'Anton', sans-serif", 1720) + "px";
if (KEYWORDS.has(wi)) el.style.color = "#FFD700";
container.appendChild(el);
gsap.set(el, { yPercent: -50 });
var entranceMode = wi % 4;
tl.set(el, { visibility: "visible" }, w.start);
if (entranceMode === 0) {
tl.fromTo(
el,
{ y: -120, opacity: 0 },
{ y: 0, opacity: 1, duration: 0.22, ease: "back.out(1.7)" },
w.start,
);
} else if (entranceMode === 1) {
tl.fromTo(
el,
{ x: -300, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.2, ease: "expo.out" },
w.start,
);
} else if (entranceMode === 2) {
tl.fromTo(
el,
{ x: 300, opacity: 0 },
{ x: 0, opacity: 1, duration: 0.2, ease: "expo.out" },
w.start,
);
} else {
tl.fromTo(
el,
{ scale: 0.4, opacity: 0 },
{ scale: 1, opacity: 1, duration: 0.24, ease: "back.out(2.2)" },
w.start,
);
}
tl.to(el, { opacity: 0, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(el, { opacity: 0, visibility: "hidden" }, w.end + 0.1);
});
tl.seek(0);
window.__timelines["caption-kinetic-slam"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-kinetic-slam",
"type": "hyperframes:component",
"title": "Kinetic Slam",
"description": "Full-screen single-word display with alternating entrance directions",
"tags": ["captions", "caption-style", "kinetic", "typography", "slam"],
"files": [
{
"path": "caption-kinetic-slam.html",
"target": "compositions/components/caption-kinetic-slam.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-kinetic-slam/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-kinetic-slam.png"
}
}
@@ -0,0 +1,267 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Matrix Decode</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=Space+Grotesk:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#matrix-decode {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#sc-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.sc-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#sc-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.sc-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.sc-word-wrap {
font-family: "Space Grotesk", sans-serif;
font-weight: 700;
font-size: 96px;
color: #00ff41;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
position: relative;
}
.sc-real {
display: none;
}
.sc-scr0 {
display: none;
}
.sc-scr1 {
display: none;
}
</style>
</head>
<body>
<div
id="matrix-decode"
data-composition-id="caption-matrix-decode"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="sc-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="sc-overlay"></div>
<div id="sc-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
function scrambleChars(seed) {
var rng = mulberry32(seed);
var result = "";
for (var i = 0; i < 4; i++) {
result += ALPHANUM[Math.floor(rng() * ALPHANUM.length)];
}
return result;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.35 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.35 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.9 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.8 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.7 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 5.0 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.7 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.5 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 7.05 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.9 },
];
var container = document.getElementById("sc-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text;
})
.join(" ");
var fontSize = fitFontSize(groupText, 96, "700", "'Space Grotesk', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "sc-group";
grp.id = "sc-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordWrap = document.createElement("span");
wordWrap.className = "sc-word-wrap";
wordWrap.id = "sc-ww-" + wi;
wordWrap.style.fontSize = fontSize + "px";
var real = document.createElement("span");
real.className = "sc-real";
real.id = "sc-real-" + wi;
real.textContent = w.text;
wordWrap.appendChild(real);
var scr0 = document.createElement("span");
scr0.className = "sc-scr0";
scr0.id = "sc-scr0-" + wi;
scr0.textContent = scrambleChars(wi * 1000 + 0);
wordWrap.appendChild(scr0);
var scr1 = document.createElement("span");
scr1.className = "sc-scr1";
scr1.id = "sc-scr1-" + wi;
scr1.textContent = scrambleChars(wi * 1000 + 1);
wordWrap.appendChild(scr1);
grp.appendChild(wordWrap);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 0 }, g.start);
tl.fromTo(grp, { opacity: 0 }, { opacity: 1, duration: 0.1 }, g.start);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var realEl = document.getElementById("sc-real-" + wi);
var scr0El = document.getElementById("sc-scr0-" + wi);
var scr1El = document.getElementById("sc-scr1-" + wi);
tl.set(scr0El, { display: "inline" }, w.start);
tl.set(scr1El, { display: "inline" }, w.start + 0.1);
tl.set(scr0El, { display: "none" }, w.start + 0.1);
tl.set(realEl, { display: "inline" }, w.start + 0.2);
tl.set(scr1El, { display: "none" }, w.start + 0.2);
});
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
window.__timelines["caption-matrix-decode"] = tl;
})();
</script>
</body>
</html>
+265
View File
@@ -0,0 +1,265 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Matrix Decode</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=Space+Grotesk:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000;
}
#matrix-decode {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
#sc-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.sc-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.85);
z-index: 1;
pointer-events: none;
}
#sc-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.sc-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.sc-word-wrap {
font-family: "Space Grotesk", sans-serif;
font-weight: 700;
font-size: 96px;
color: #00ff41;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
position: relative;
}
.sc-real {
display: none;
}
.sc-scr0 {
display: none;
}
.sc-scr1 {
display: none;
}
</style>
</head>
<body>
<div
id="matrix-decode"
data-composition-id="caption-matrix-decode"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="sc-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="sc-overlay"></div>
<div id="sc-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var ALPHANUM = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
function scrambleChars(seed) {
var rng = mulberry32(seed);
var result = "";
for (var i = 0; i < 4; i++) {
result += ALPHANUM[Math.floor(rng() * ALPHANUM.length)];
}
return result;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.35 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.35 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.9 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.8 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.7 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 5.0 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.7 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.5 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 7.05 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.9 },
];
var container = document.getElementById("sc-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text;
})
.join(" ");
var fontSize = fitFontSize(groupText, 96, "700", "'Space Grotesk', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "sc-group";
grp.id = "sc-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordWrap = document.createElement("span");
wordWrap.className = "sc-word-wrap";
wordWrap.id = "sc-ww-" + wi;
wordWrap.style.fontSize = fontSize + "px";
var real = document.createElement("span");
real.className = "sc-real";
real.id = "sc-real-" + wi;
real.textContent = w.text;
wordWrap.appendChild(real);
var scr0 = document.createElement("span");
scr0.className = "sc-scr0";
scr0.id = "sc-scr0-" + wi;
scr0.textContent = scrambleChars(wi * 1000 + 0);
wordWrap.appendChild(scr0);
var scr1 = document.createElement("span");
scr1.className = "sc-scr1";
scr1.id = "sc-scr1-" + wi;
scr1.textContent = scrambleChars(wi * 1000 + 1);
wordWrap.appendChild(scr1);
grp.appendChild(wordWrap);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 0 }, g.start);
tl.fromTo(grp, { opacity: 0 }, { opacity: 1, duration: 0.1 }, g.start);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var realEl = document.getElementById("sc-real-" + wi);
var scr0El = document.getElementById("sc-scr0-" + wi);
var scr1El = document.getElementById("sc-scr1-" + wi);
tl.set(scr0El, { display: "inline" }, w.start);
tl.set(scr1El, { display: "inline" }, w.start + 0.1);
tl.set(scr0El, { display: "none" }, w.start + 0.1);
tl.set(realEl, { display: "inline" }, w.start + 0.2);
tl.set(scr1El, { display: "none" }, w.start + 0.2);
});
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
window.__timelines["caption-matrix-decode"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-matrix-decode",
"type": "hyperframes:component",
"title": "Matrix Decode",
"description": "Character scramble animation before text reveal",
"tags": ["captions", "caption-style", "matrix", "scramble", "decode"],
"files": [
{
"path": "caption-matrix-decode.html",
"target": "compositions/components/caption-matrix-decode.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-matrix-decode/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-matrix-decode.png"
}
}
@@ -0,0 +1,533 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Neon Accent</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=Montserrat:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Montserrat", Arial, sans-serif;
}
#neon-accent {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 260px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 260px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 1400px;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
}
.caption-word {
display: inline-block;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 800;
font-size: 88px;
line-height: 1.1;
letter-spacing: 0;
text-transform: uppercase;
}
.caption-word.accent {
font-size: 106px;
}
</style>
</head>
<body>
<div
id="neon-accent"
data-composition-id="caption-neon-accent"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var BASE_FONT_SIZE = 88;
var ACCENT_FONT_SIZE = 106;
var MIN_FONT_SIZE = 62;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 16;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 7 / 30;
var WIGGLE_DISTANCE = 14;
var WIGGLE_CYCLE = 2 / 1.3;
var FONT_WIDTH_SAFETY = 1.14;
var PRIMARY_COLOR = "#FFFFFF";
var ACCENT_COLORS = ["#53FF01", "#FF0002", "#FCFF00"];
var TRANSITION_WORDS = new Set([
"the",
"a",
"an",
"and",
"or",
"but",
"is",
"are",
"was",
"were",
"to",
"of",
"in",
"on",
"at",
"for",
"with",
"by",
"from",
"as",
"it",
"its",
"this",
"that",
"these",
"those",
"be",
"been",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"must",
"can",
"if",
"then",
"so",
"just",
"not",
"no",
"yes",
"up",
"out",
"about",
"lets",
"you",
"write",
"render",
]);
var KEYWORDS = new Set(["hyperframes", "html", "professional", "code", "cinema"]);
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
gsap.registerPlugin && gsap.registerPlugin();
function cleanText(text) {
return String(text || "").trim();
}
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
return {
text: cleanText(item.word || item.text),
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function seededRandom(seed) {
var x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
}
function isKeyword(text) {
return KEYWORDS.has(text.toLowerCase().replace(/[^a-z]/g, ""));
}
function assignWordColors(words, groupIndex) {
return words.map(function (word) {
if (isKeyword(word.text)) {
var colorIndex = Math.floor(seededRandom(groupIndex * 23 + 7) * ACCENT_COLORS.length);
return ACCENT_COLORS[colorIndex];
}
var clean = word.text.toLowerCase().replace(/[^a-z']/g, "");
if (TRANSITION_WORDS.has(clean)) return PRIMARY_COLOR;
var rand = seededRandom(groupIndex * 7 + words.indexOf(word));
if (rand > 0.6) {
var ci = Math.floor(rand * ACCENT_COLORS.length) % ACCENT_COLORS.length;
return ACCENT_COLORS[ci];
}
return PRIMARY_COLOR;
});
}
function measureWord(word, color, fontSize) {
var size = color === PRIMARY_COLOR ? fontSize : fontSize * 1.2;
measureContext.font = "800 " + size + "px Montserrat";
return measureContext.measureText(word.text.toUpperCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, colors, fontSize) {
return words.reduce(function (total, word, index) {
return (
total +
measureWord(word, colors[index] || PRIMARY_COLOR, fontSize) +
(index ? WORD_SPACING : 0)
);
}, 0);
}
function splitLinesForGroup(words, colors, fontSize) {
var maxLineWidth = SAFE_ZONE_WIDTH;
if (
words.some(function (word, i) {
return measureWord(word, colors[i] || PRIMARY_COLOR, fontSize) > maxLineWidth;
})
)
return null;
if (measureLineWidth(words, colors, fontSize) <= maxLineWidth)
return [{ words: words, colors: colors }];
var best = null;
var bestDiff = Infinity;
for (var split = 1; split < words.length; split++) {
var fw = words.slice(0, split),
sw = words.slice(split);
var fc = colors.slice(0, split),
sc = colors.slice(split);
var fW = measureLineWidth(fw, fc, fontSize);
var sW = measureLineWidth(sw, sc, fontSize);
if (fW <= maxLineWidth && sW <= maxLineWidth) {
var diff = Math.abs(fW - sW);
if (diff < bestDiff) {
best = [
{ words: fw, colors: fc },
{ words: sw, colors: sc },
];
bestDiff = diff;
}
}
}
return best;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
var exceedsWords = testGroup.length > MAX_WORDS_PER_GROUP;
var colors = assignWordColors(testGroup, groups.length);
var exceedsFit = !splitLinesForGroup(testGroup, colors, BASE_FONT_SIZE);
if (exceedsWords || exceedsFit) {
if (current.length) groups.push(makeGroup(current, groups.length));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current, groups.length));
current = [];
}
});
if (current.length) groups.push(makeGroup(current, groups.length));
return groups;
}
function makeGroup(words, groupIndex) {
var colors = assignWordColors(words, groupIndex);
var groupText = words
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, BASE_FONT_SIZE, "800", "Montserrat", SAFE_ZONE_WIDTH);
while (fontSize > MIN_FONT_SIZE && !splitLinesForGroup(words, colors, fontSize)) {
fontSize -= 2;
}
return {
words: words.slice(),
colors: colors,
fontSize: fontSize,
lines: splitLinesForGroup(words, colors, fontSize) || [{ words: words, colors: colors }],
start: words[0].start,
end: words[words.length - 1].end,
};
}
function shadowForColor(color) {
var hex = color.replace("#", "");
var r = parseInt(hex.slice(0, 2), 16);
var g = parseInt(hex.slice(2, 4), 16);
var b = parseInt(hex.slice(4, 6), 16);
var glow =
"0 0 4px rgba(" +
r +
"," +
g +
"," +
b +
",1), 0 0 10px rgba(" +
r +
"," +
g +
"," +
b +
",0.9), 0 0 20px rgba(" +
r +
"," +
g +
"," +
b +
",0.7), 0 0 40px rgba(" +
r +
"," +
g +
"," +
b +
",0.4)";
return "0 8px 16px rgba(0,0,0,0.8), 0 16px 40px rgba(0,0,0,0.6), " + glow;
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupEl = document.createElement("div");
var copyEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
copyEl.className = "caption-copy";
group.lines.slice(0, 2).forEach(function (line) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
line.words.forEach(function (word, wordIndex) {
var color = line.colors[wordIndex] || PRIMARY_COLOR;
var wordEl = document.createElement("span");
wordEl.className = color === PRIMARY_COLOR ? "caption-word" : "caption-word accent";
wordEl.textContent = word.text.toUpperCase();
wordEl.style.color = color;
wordEl.style.fontSize =
color === PRIMARY_COLOR ? group.fontSize + "px" : group.fontSize * 1.2 + "px";
wordEl.style.textShadow = shadowForColor(color);
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
groupEl.appendChild(copyEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
var direction = groupIndex % 2 === 0 ? -1 : 1;
var D = WIGGLE_DISTANCE * direction;
var wiggleCycles = Math.max(1, Math.ceil((visibleEnd - visibleStart) / WIGGLE_CYCLE));
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scale: 0.65, x: 0, y: 0 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scale: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
tl.to(
groupEl,
{
x: D,
y: D,
duration: WIGGLE_CYCLE / 2,
ease: "sine.inOut",
yoyo: true,
repeat: Math.max(0, wiggleCycles * 2 - 1),
force3D: true,
},
visibleStart,
);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-neon-accent"] = tl;
</script>
</body>
</html>
+531
View File
@@ -0,0 +1,531 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Neon Accent</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=Montserrat:wght@800&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000000;
font-family: "Montserrat", Arial, sans-serif;
}
#neon-accent {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000000;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 260px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 260px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 1400px;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
}
.caption-word {
display: inline-block;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 800;
font-size: 88px;
line-height: 1.1;
letter-spacing: 0;
text-transform: uppercase;
}
.caption-word.accent {
font-size: 106px;
}
</style>
</head>
<body>
<div
id="neon-accent"
data-composition-id="caption-neon-accent"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var BASE_FONT_SIZE = 88;
var ACCENT_FONT_SIZE = 106;
var MIN_FONT_SIZE = 62;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 16;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 7 / 30;
var WIGGLE_DISTANCE = 14;
var WIGGLE_CYCLE = 2 / 1.3;
var FONT_WIDTH_SAFETY = 1.14;
var PRIMARY_COLOR = "#FFFFFF";
var ACCENT_COLORS = ["#53FF01", "#FF0002", "#FCFF00"];
var TRANSITION_WORDS = new Set([
"the",
"a",
"an",
"and",
"or",
"but",
"is",
"are",
"was",
"were",
"to",
"of",
"in",
"on",
"at",
"for",
"with",
"by",
"from",
"as",
"it",
"its",
"this",
"that",
"these",
"those",
"be",
"been",
"have",
"has",
"had",
"do",
"does",
"did",
"will",
"would",
"could",
"should",
"may",
"might",
"must",
"can",
"if",
"then",
"so",
"just",
"not",
"no",
"yes",
"up",
"out",
"about",
"lets",
"you",
"write",
"render",
]);
var KEYWORDS = new Set(["hyperframes", "html", "professional", "code", "cinema"]);
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
gsap.registerPlugin && gsap.registerPlugin();
function cleanText(text) {
return String(text || "").trim();
}
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
return {
text: cleanText(item.word || item.text),
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function seededRandom(seed) {
var x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
}
function isKeyword(text) {
return KEYWORDS.has(text.toLowerCase().replace(/[^a-z]/g, ""));
}
function assignWordColors(words, groupIndex) {
return words.map(function (word) {
if (isKeyword(word.text)) {
var colorIndex = Math.floor(seededRandom(groupIndex * 23 + 7) * ACCENT_COLORS.length);
return ACCENT_COLORS[colorIndex];
}
var clean = word.text.toLowerCase().replace(/[^a-z']/g, "");
if (TRANSITION_WORDS.has(clean)) return PRIMARY_COLOR;
var rand = seededRandom(groupIndex * 7 + words.indexOf(word));
if (rand > 0.6) {
var ci = Math.floor(rand * ACCENT_COLORS.length) % ACCENT_COLORS.length;
return ACCENT_COLORS[ci];
}
return PRIMARY_COLOR;
});
}
function measureWord(word, color, fontSize) {
var size = color === PRIMARY_COLOR ? fontSize : fontSize * 1.2;
measureContext.font = "800 " + size + "px Montserrat";
return measureContext.measureText(word.text.toUpperCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, colors, fontSize) {
return words.reduce(function (total, word, index) {
return (
total +
measureWord(word, colors[index] || PRIMARY_COLOR, fontSize) +
(index ? WORD_SPACING : 0)
);
}, 0);
}
function splitLinesForGroup(words, colors, fontSize) {
var maxLineWidth = SAFE_ZONE_WIDTH;
if (
words.some(function (word, i) {
return measureWord(word, colors[i] || PRIMARY_COLOR, fontSize) > maxLineWidth;
})
)
return null;
if (measureLineWidth(words, colors, fontSize) <= maxLineWidth)
return [{ words: words, colors: colors }];
var best = null;
var bestDiff = Infinity;
for (var split = 1; split < words.length; split++) {
var fw = words.slice(0, split),
sw = words.slice(split);
var fc = colors.slice(0, split),
sc = colors.slice(split);
var fW = measureLineWidth(fw, fc, fontSize);
var sW = measureLineWidth(sw, sc, fontSize);
if (fW <= maxLineWidth && sW <= maxLineWidth) {
var diff = Math.abs(fW - sW);
if (diff < bestDiff) {
best = [
{ words: fw, colors: fc },
{ words: sw, colors: sc },
];
bestDiff = diff;
}
}
}
return best;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
var exceedsWords = testGroup.length > MAX_WORDS_PER_GROUP;
var colors = assignWordColors(testGroup, groups.length);
var exceedsFit = !splitLinesForGroup(testGroup, colors, BASE_FONT_SIZE);
if (exceedsWords || exceedsFit) {
if (current.length) groups.push(makeGroup(current, groups.length));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current, groups.length));
current = [];
}
});
if (current.length) groups.push(makeGroup(current, groups.length));
return groups;
}
function makeGroup(words, groupIndex) {
var colors = assignWordColors(words, groupIndex);
var groupText = words
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, BASE_FONT_SIZE, "800", "Montserrat", SAFE_ZONE_WIDTH);
while (fontSize > MIN_FONT_SIZE && !splitLinesForGroup(words, colors, fontSize)) {
fontSize -= 2;
}
return {
words: words.slice(),
colors: colors,
fontSize: fontSize,
lines: splitLinesForGroup(words, colors, fontSize) || [{ words: words, colors: colors }],
start: words[0].start,
end: words[words.length - 1].end,
};
}
function shadowForColor(color) {
var hex = color.replace("#", "");
var r = parseInt(hex.slice(0, 2), 16);
var g = parseInt(hex.slice(2, 4), 16);
var b = parseInt(hex.slice(4, 6), 16);
var glow =
"0 0 4px rgba(" +
r +
"," +
g +
"," +
b +
",1), 0 0 10px rgba(" +
r +
"," +
g +
"," +
b +
",0.9), 0 0 20px rgba(" +
r +
"," +
g +
"," +
b +
",0.7), 0 0 40px rgba(" +
r +
"," +
g +
"," +
b +
",0.4)";
return "0 8px 16px rgba(0,0,0,0.8), 0 16px 40px rgba(0,0,0,0.6), " + glow;
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupEl = document.createElement("div");
var copyEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
copyEl.className = "caption-copy";
group.lines.slice(0, 2).forEach(function (line) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
line.words.forEach(function (word, wordIndex) {
var color = line.colors[wordIndex] || PRIMARY_COLOR;
var wordEl = document.createElement("span");
wordEl.className = color === PRIMARY_COLOR ? "caption-word" : "caption-word accent";
wordEl.textContent = word.text.toUpperCase();
wordEl.style.color = color;
wordEl.style.fontSize =
color === PRIMARY_COLOR ? group.fontSize + "px" : group.fontSize * 1.2 + "px";
wordEl.style.textShadow = shadowForColor(color);
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
groupEl.appendChild(copyEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
var direction = groupIndex % 2 === 0 ? -1 : 1;
var D = WIGGLE_DISTANCE * direction;
var wiggleCycles = Math.max(1, Math.ceil((visibleEnd - visibleStart) / WIGGLE_CYCLE));
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scale: 0.65, x: 0, y: 0 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scale: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
tl.to(
groupEl,
{
x: D,
y: D,
duration: WIGGLE_CYCLE / 2,
ease: "sine.inOut",
yoyo: true,
repeat: Math.max(0, wiggleCycles * 2 - 1),
force3D: true,
},
visibleStart,
);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-neon-accent"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-neon-accent",
"type": "hyperframes:component",
"title": "Neon Accent",
"description": "Multi-color neon glow accents with wiggle drift animation",
"tags": ["captions", "caption-style", "neon", "glow", "accent"],
"files": [
{
"path": "caption-neon-accent.html",
"target": "compositions/components/caption-neon-accent.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-neon-accent/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-neon-accent.png"
}
}
@@ -0,0 +1,233 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Neon Glow</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=Outfit:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#neon-glow {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#ne-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.ne-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#ne-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.ne-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.ne-word {
font-family: "Outfit", sans-serif;
font-weight: 900;
font-size: 96px;
text-transform: uppercase;
color: rgba(0, 255, 240, 0.14);
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
}
</style>
</head>
<body>
<div
id="neon-glow"
data-composition-id="caption-neon-glow"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="ne-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="ne-overlay"></div>
<div id="ne-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 },
];
// Keywords get pink glow; others get cyan glow
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var container = document.getElementById("ne-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, 96, "900", "'Outfit', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "ne-group";
grp.id = "ne-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "ne-word";
span.id = "ne-w-" + wi;
span.textContent = w.text;
span.style.fontSize = fontSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 0 }, g.start);
tl.fromTo(grp, { opacity: 0 }, { opacity: 1, duration: 0.05, ease: "steps(1)" }, g.start);
tl.fromTo(grp, { x: -8 }, { x: 0, duration: 0.14, ease: "power3.out" }, g.start + 0.05);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var el = document.getElementById("ne-w-" + wi);
var isKW = KEYWORDS.has(wi);
var activeColor = isKW ? "#FF0099" : "#00FFF0";
tl.to(
el,
{
color: activeColor,
textShadow:
"0 0 10px " +
activeColor +
", 0 0 35px " +
activeColor +
", 0 0 90px " +
activeColor,
duration: 0.06,
},
w.start,
);
tl.to(el, { color: "rgba(0,255,240,0.14)", textShadow: "none", duration: 0.06 }, w.end);
});
tl.to(grp, { opacity: 0, x: 6, duration: 0.1, ease: "steps(1)" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
window.__timelines["caption-neon-glow"] = tl;
})();
</script>
</body>
</html>
+231
View File
@@ -0,0 +1,231 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Neon Glow</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=Outfit:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000;
}
#neon-glow {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
#ne-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.ne-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.75);
z-index: 1;
pointer-events: none;
}
#ne-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.ne-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 80px;
opacity: 0;
visibility: hidden;
}
.ne-word {
font-family: "Outfit", sans-serif;
font-weight: 900;
font-size: 96px;
text-transform: uppercase;
color: rgba(0, 255, 240, 0.14);
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
}
</style>
</head>
<body>
<div
id="neon-glow"
data-composition-id="caption-neon-glow"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="ne-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="ne-overlay"></div>
<div id="ne-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 },
];
// Keywords get pink glow; others get cyan glow
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var container = document.getElementById("ne-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, 96, "900", "'Outfit', sans-serif", 1760);
var grp = document.createElement("div");
grp.className = "ne-group";
grp.id = "ne-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "ne-word";
span.id = "ne-w-" + wi;
span.textContent = w.text;
span.style.fontSize = fontSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 0 }, g.start);
tl.fromTo(grp, { opacity: 0 }, { opacity: 1, duration: 0.05, ease: "steps(1)" }, g.start);
tl.fromTo(grp, { x: -8 }, { x: 0, duration: 0.14, ease: "power3.out" }, g.start + 0.05);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var el = document.getElementById("ne-w-" + wi);
var isKW = KEYWORDS.has(wi);
var activeColor = isKW ? "#FF0099" : "#00FFF0";
tl.to(
el,
{
color: activeColor,
textShadow:
"0 0 10px " +
activeColor +
", 0 0 35px " +
activeColor +
", 0 0 90px " +
activeColor,
duration: 0.06,
},
w.start,
);
tl.to(el, { color: "rgba(0,255,240,0.14)", textShadow: "none", duration: 0.06 }, w.end);
});
tl.to(grp, { opacity: 0, x: 6, duration: 0.1, ease: "steps(1)" }, g.end - 0.1);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
window.__timelines["caption-neon-glow"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-neon-glow",
"type": "hyperframes:component",
"title": "Neon Glow",
"description": "Cyan and magenta neon glow with keyword accent colors",
"tags": ["captions", "caption-style", "neon", "glow", "gaming"],
"files": [
{
"path": "caption-neon-glow.html",
"target": "compositions/components/caption-neon-glow.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-neon-glow/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-neon-glow.png"
}
}
@@ -0,0 +1,375 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Parallax Layers</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=Instrument+Serif:ital@0;1&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Instrument Serif", serif;
}
#parallax-layers {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#bg-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Behind captions: big red 3D text */
.behind-caption-layer {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
transform: translateZ(0);
}
/* Front captions */
.front-caption-layer {
position: absolute;
inset: 0;
z-index: 30;
pointer-events: none;
transform: translateZ(0);
}
.behind-safe-zone {
position: absolute;
top: 40px;
left: 0;
width: 1920px;
height: 500px;
}
.front-safe-zone {
position: absolute;
top: 700px;
left: 0;
width: 1920px;
height: 300px;
}
.caption-block {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
opacity: 0;
transform-origin: 50% 50%;
will-change: transform, opacity;
backface-visibility: hidden;
}
.caption-line {
display: flex;
align-items: baseline;
justify-content: center;
gap: 14px;
line-height: 1.1;
white-space: nowrap;
}
.caption-line--behind {
transform: scaleY(2.8);
transform-origin: 50% 0%;
}
.word {
display: inline-block;
will-change: transform, opacity;
backface-visibility: hidden;
}
.word--behind {
font-family: "Instrument Serif", serif;
font-size: 220px;
font-weight: 400;
font-style: normal;
line-height: 0.9;
color: #e50914;
-webkit-text-stroke: 5px #e50914;
text-shadow: 2px 4px 4px #a30610;
}
.word--front-normal {
font-family: "Instrument Serif", serif;
font-size: 130px;
font-weight: 400;
font-style: normal;
color: #eeeeee;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
}
.word--front-italic {
font-family: "Instrument Serif", serif;
font-size: 130px;
font-weight: 400;
font-style: italic;
color: #eeeeee;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
}
</style>
</head>
<body>
<div
id="parallax-layers"
data-composition-id="caption-parallax-layers"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bg-video"
muted
playsinline
preload="auto"
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="behind-caption-layer" aria-hidden="true">
<div id="behind-stage" class="behind-safe-zone"></div>
</div>
<div class="front-caption-layer" aria-hidden="true">
<div id="front-stage" class="front-safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var BEHIND_WIDTH = 1800;
var FRONT_WIDTH = 1750;
var ENTRY_DUR = 0.1;
var SLIDE_DUR = 0.2;
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var W = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// behind=red 3D emphasis words, front=normal white words
var BLOCKS = [
{ behind: null, front: [[0], [1], [2]] },
{ behind: [[3]], front: [[4], [5]] },
{ behind: [[6]], front: [[7]] },
{ behind: [[8]], front: [[9], [10], [11]] },
{ behind: [[12]], front: [[13], [14]] },
{ behind: [[15]], front: [[16]] },
{ behind: null, front: [[17], [18]] },
{ behind: null, front: [[19], [20], [21], [22]] },
{ behind: [[24]], front: [[23], [25], [26]] },
{ behind: [[27]], front: null },
];
var behindStage = document.getElementById("behind-stage");
var frontStage = document.getElementById("front-stage");
BLOCKS.forEach(function (block, bi) {
var bEl = document.createElement("div");
bEl.className = "caption-block";
bEl.id = "bb" + bi;
if (block.behind) {
var behindText = block.behind
.map(function (wi_arr) {
return W[wi_arr[0]].text;
})
.join(" ");
var behindSize = fitFontSize(behindText, 220, "400", "Instrument Serif", BEHIND_WIDTH);
var bLine = document.createElement("div");
bLine.className = "caption-line caption-line--behind";
block.behind.forEach(function (wi_arr, wi) {
var span = document.createElement("span");
span.className = "word word--behind";
span.id = "bb" + bi + "w" + wi;
span.textContent = W[wi_arr[0]].text;
span.style.fontSize = behindSize + "px";
bLine.appendChild(span);
});
bEl.appendChild(bLine);
}
behindStage.appendChild(bEl);
var fEl = document.createElement("div");
fEl.className = "caption-block";
fEl.id = "fb" + bi;
if (block.front) {
var frontText = block.front
.map(function (wi_arr) {
return W[wi_arr[0]].text;
})
.join(" ");
var frontSize = fitFontSize(frontText, 130, "400", "Instrument Serif", FRONT_WIDTH);
var fLine = document.createElement("div");
fLine.className = "caption-line";
block.front.forEach(function (wi_arr, wi) {
var span = document.createElement("span");
var isItalic = wi_arr[0] === 7 || wi_arr[0] === 18; // frame., timeline.
span.className = "word " + (isItalic ? "word--front-italic" : "word--front-normal");
span.id = "fb" + bi + "w" + wi;
span.textContent = W[wi_arr[0]].text;
span.style.fontSize = frontSize + "px";
fLine.appendChild(span);
});
fEl.appendChild(fLine);
}
frontStage.appendChild(fEl);
});
var allBehind = BLOCKS.map(function (_, i) {
return document.getElementById("bb" + i);
});
var allFront = BLOCKS.map(function (_, i) {
return document.getElementById("fb" + i);
});
function applyBehindScales() {
BLOCKS.forEach(function (block, bi) {
if (block.behind) {
var line = allBehind[bi].querySelector(".caption-line");
if (line) {
var natW = line.scrollWidth;
if (natW > 0) {
var s = BEHIND_WIDTH / natW;
line.style.transform = "scaleX(" + s + ") scaleY(2.8)";
}
}
}
});
}
applyBehindScales();
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
function blockStart(bi) {
var b = BLOCKS[bi];
var firstIdx = b.behind ? b.behind[0][0] : b.front[0][0];
return W[firstIdx].start;
}
BLOCKS.forEach(function (block, bi) {
var start = blockStart(bi);
var nextStart = bi < BLOCKS.length - 1 ? blockStart(bi + 1) : DURATION;
var bEl = allBehind[bi];
var fEl = allFront[bi];
allBehind.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
allFront.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
if (block.behind) {
tl.set(bEl, { opacity: 1 }, start);
block.behind.forEach(function (wi_arr, wi) {
var wordEl = document.getElementById("bb" + bi + "w" + wi);
var wordStart = W[wi_arr[0]].start;
tl.set(wordEl, { opacity: 0 }, start);
tl.to(wordEl, { opacity: 1, duration: ENTRY_DUR, ease: "power2.out" }, wordStart);
});
}
if (block.front) {
tl.set(fEl, { opacity: 1 }, start);
block.front.forEach(function (wi_arr, wi) {
var wordEl = document.getElementById("fb" + bi + "w" + wi);
var wordStart = W[wi_arr[0]].start;
tl.set(wordEl, { opacity: 0 }, start);
tl.set(wordEl, { opacity: 1 }, wordStart);
});
}
tl.set(bEl, { opacity: 0 }, nextStart);
tl.set(fEl, { opacity: 0 }, nextStart);
});
tl.to({}, { duration: DURATION }, 0);
window.__timelines["caption-parallax-layers"] = tl;
document.fonts.ready.then(function () {
applyBehindScales();
});
</script>
</body>
</html>
+373
View File
@@ -0,0 +1,373 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Parallax Layers</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=Instrument+Serif:ital@0;1&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000;
font-family: "Instrument Serif", serif;
}
#parallax-layers {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000;
}
#bg-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
/* Behind captions: big red 3D text */
.behind-caption-layer {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
transform: translateZ(0);
}
/* Front captions */
.front-caption-layer {
position: absolute;
inset: 0;
z-index: 30;
pointer-events: none;
transform: translateZ(0);
}
.behind-safe-zone {
position: absolute;
top: 40px;
left: 0;
width: 1920px;
height: 500px;
}
.front-safe-zone {
position: absolute;
top: 700px;
left: 0;
width: 1920px;
height: 300px;
}
.caption-block {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
opacity: 0;
transform-origin: 50% 50%;
will-change: transform, opacity;
backface-visibility: hidden;
}
.caption-line {
display: flex;
align-items: baseline;
justify-content: center;
gap: 14px;
line-height: 1.1;
white-space: nowrap;
}
.caption-line--behind {
transform: scaleY(2.8);
transform-origin: 50% 0%;
}
.word {
display: inline-block;
will-change: transform, opacity;
backface-visibility: hidden;
}
.word--behind {
font-family: "Instrument Serif", serif;
font-size: 220px;
font-weight: 400;
font-style: normal;
line-height: 0.9;
color: #e50914;
-webkit-text-stroke: 5px #e50914;
text-shadow: 2px 4px 4px #a30610;
}
.word--front-normal {
font-family: "Instrument Serif", serif;
font-size: 130px;
font-weight: 400;
font-style: normal;
color: #eeeeee;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
}
.word--front-italic {
font-family: "Instrument Serif", serif;
font-size: 130px;
font-weight: 400;
font-style: italic;
color: #eeeeee;
text-shadow:
0 2px 12px rgba(0, 0, 0, 0.6),
0 4px 24px rgba(0, 0, 0, 0.35);
}
</style>
</head>
<body>
<div
id="parallax-layers"
data-composition-id="caption-parallax-layers"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bg-video"
muted
playsinline
preload="auto"
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="behind-caption-layer" aria-hidden="true">
<div id="behind-stage" class="behind-safe-zone"></div>
</div>
<div class="front-caption-layer" aria-hidden="true">
<div id="front-stage" class="front-safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var BEHIND_WIDTH = 1800;
var FRONT_WIDTH = 1750;
var ENTRY_DUR = 0.1;
var SLIDE_DUR = 0.2;
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var W = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// behind=red 3D emphasis words, front=normal white words
var BLOCKS = [
{ behind: null, front: [[0], [1], [2]] },
{ behind: [[3]], front: [[4], [5]] },
{ behind: [[6]], front: [[7]] },
{ behind: [[8]], front: [[9], [10], [11]] },
{ behind: [[12]], front: [[13], [14]] },
{ behind: [[15]], front: [[16]] },
{ behind: null, front: [[17], [18]] },
{ behind: null, front: [[19], [20], [21], [22]] },
{ behind: [[24]], front: [[23], [25], [26]] },
{ behind: [[27]], front: null },
];
var behindStage = document.getElementById("behind-stage");
var frontStage = document.getElementById("front-stage");
BLOCKS.forEach(function (block, bi) {
var bEl = document.createElement("div");
bEl.className = "caption-block";
bEl.id = "bb" + bi;
if (block.behind) {
var behindText = block.behind
.map(function (wi_arr) {
return W[wi_arr[0]].text;
})
.join(" ");
var behindSize = fitFontSize(behindText, 220, "400", "Instrument Serif", BEHIND_WIDTH);
var bLine = document.createElement("div");
bLine.className = "caption-line caption-line--behind";
block.behind.forEach(function (wi_arr, wi) {
var span = document.createElement("span");
span.className = "word word--behind";
span.id = "bb" + bi + "w" + wi;
span.textContent = W[wi_arr[0]].text;
span.style.fontSize = behindSize + "px";
bLine.appendChild(span);
});
bEl.appendChild(bLine);
}
behindStage.appendChild(bEl);
var fEl = document.createElement("div");
fEl.className = "caption-block";
fEl.id = "fb" + bi;
if (block.front) {
var frontText = block.front
.map(function (wi_arr) {
return W[wi_arr[0]].text;
})
.join(" ");
var frontSize = fitFontSize(frontText, 130, "400", "Instrument Serif", FRONT_WIDTH);
var fLine = document.createElement("div");
fLine.className = "caption-line";
block.front.forEach(function (wi_arr, wi) {
var span = document.createElement("span");
var isItalic = wi_arr[0] === 7 || wi_arr[0] === 18; // frame., timeline.
span.className = "word " + (isItalic ? "word--front-italic" : "word--front-normal");
span.id = "fb" + bi + "w" + wi;
span.textContent = W[wi_arr[0]].text;
span.style.fontSize = frontSize + "px";
fLine.appendChild(span);
});
fEl.appendChild(fLine);
}
frontStage.appendChild(fEl);
});
var allBehind = BLOCKS.map(function (_, i) {
return document.getElementById("bb" + i);
});
var allFront = BLOCKS.map(function (_, i) {
return document.getElementById("fb" + i);
});
function applyBehindScales() {
BLOCKS.forEach(function (block, bi) {
if (block.behind) {
var line = allBehind[bi].querySelector(".caption-line");
if (line) {
var natW = line.scrollWidth;
if (natW > 0) {
var s = BEHIND_WIDTH / natW;
line.style.transform = "scaleX(" + s + ") scaleY(2.8)";
}
}
}
});
}
applyBehindScales();
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
function blockStart(bi) {
var b = BLOCKS[bi];
var firstIdx = b.behind ? b.behind[0][0] : b.front[0][0];
return W[firstIdx].start;
}
BLOCKS.forEach(function (block, bi) {
var start = blockStart(bi);
var nextStart = bi < BLOCKS.length - 1 ? blockStart(bi + 1) : DURATION;
var bEl = allBehind[bi];
var fEl = allFront[bi];
allBehind.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
allFront.forEach(function (other, oi) {
if (oi !== bi) tl.set(other, { opacity: 0 }, start);
});
if (block.behind) {
tl.set(bEl, { opacity: 1 }, start);
block.behind.forEach(function (wi_arr, wi) {
var wordEl = document.getElementById("bb" + bi + "w" + wi);
var wordStart = W[wi_arr[0]].start;
tl.set(wordEl, { opacity: 0 }, start);
tl.to(wordEl, { opacity: 1, duration: ENTRY_DUR, ease: "power2.out" }, wordStart);
});
}
if (block.front) {
tl.set(fEl, { opacity: 1 }, start);
block.front.forEach(function (wi_arr, wi) {
var wordEl = document.getElementById("fb" + bi + "w" + wi);
var wordStart = W[wi_arr[0]].start;
tl.set(wordEl, { opacity: 0 }, start);
tl.set(wordEl, { opacity: 1 }, wordStart);
});
}
tl.set(bEl, { opacity: 0 }, nextStart);
tl.set(fEl, { opacity: 0 }, nextStart);
});
tl.to({}, { duration: DURATION }, 0);
window.__timelines["caption-parallax-layers"] = tl;
document.fonts.ready.then(function () {
applyBehindScales();
});
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-parallax-layers",
"type": "hyperframes:component",
"title": "Parallax Layers",
"description": "Behind-subject 3D text layering with vertical stretch effect",
"tags": ["captions", "caption-style", "parallax", "3d", "depth"],
"files": [
{
"path": "caption-parallax-layers.html",
"target": "compositions/components/caption-parallax-layers.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-parallax-layers/preview.mp4?v=1779051692",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-parallax-layers.png"
}
}
@@ -0,0 +1,313 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Particle Burst</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=Outfit:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#particle-burst {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#bs-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.bs-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#bs-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.bs-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.bs-word {
font-family: "Outfit", sans-serif;
font-weight: 900;
font-size: 88px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.45);
display: inline-block;
letter-spacing: 0.05em;
line-height: 1;
}
.bs-particle {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
opacity: 0;
pointer-events: none;
z-index: 20;
left: 960px;
bottom: 180px;
}
</style>
</head>
<body>
<div
id="particle-burst"
data-composition-id="caption-particle-burst"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bs-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="bs-overlay"></div>
<div id="bs-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// Keywords get particle burst + gold color
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.35, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var PARTICLE_COLORS = [
"#FFD700",
"#FF6B35",
"#FF3C78",
"#9B5DE5",
"#00BBF9",
"#00F5D4",
"#ffffff",
"#F15BB5",
];
var NUM_PARTICLES = 10;
var container = document.getElementById("bs-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, 88, "900", "'Outfit', sans-serif", 1720);
var grp = document.createElement("div");
grp.className = "bs-group";
grp.id = "bs-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "bs-word";
span.id = "bs-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = fontSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
});
// Create particles for keyword words
var particleData = {};
WORDS.forEach(function (w, wi) {
if (!KEYWORDS.has(wi)) return;
var rng = mulberry32(wi * 137 + 99);
particleData[wi] = [];
for (var p = 0; p < NUM_PARTICLES; p++) {
var angle = (p / NUM_PARTICLES) * Math.PI * 2 + rng() * 0.6;
var dist = 120 + rng() * 200;
var sz = 4 + rng() * 8;
var dx = Math.cos(angle) * dist;
var dy = -Math.sin(angle) * dist;
var dot = document.createElement("div");
dot.className = "bs-particle";
dot.id = "bs-p-" + wi + "-" + p;
dot.style.backgroundColor = PARTICLE_COLORS[p % PARTICLE_COLORS.length];
dot.style.width = sz.toFixed(1) + "px";
dot.style.height = sz.toFixed(1) + "px";
container.appendChild(dot);
particleData[wi].push({ el: dot, dx: dx, dy: dy });
}
});
GROUPS.forEach(function (g, gi) {
var grp = document.getElementById("bs-grp-" + gi);
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0, scale: 0.92 },
{ opacity: 1, scale: 1, duration: 0.2, ease: "back.out(1.5)" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("bs-w-" + wi);
var isKW = KEYWORDS.has(wi);
tl.to(
wordEl,
{ color: isKW ? "#FFD700" : "#ffffff", scale: isKW ? 1.12 : 1.05, duration: 0.08 },
w.start,
);
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", scale: 1.0, duration: 0.1 }, w.end);
if (isKW && particleData[wi]) {
particleData[wi].forEach(function (pd, pi) {
tl.set(pd.el, { x: 0, y: 0, opacity: 0 }, w.start);
tl.to(
pd.el,
{ x: pd.dx, y: pd.dy, opacity: 1, duration: 0.12, ease: "power3.out" },
w.start + pi * 0.018,
);
tl.to(
pd.el,
{ opacity: 0, duration: 0.45, ease: "power1.in" },
w.start + 0.12 + pi * 0.018,
);
tl.set(pd.el, { opacity: 0, x: 0, y: 0 }, w.start + 0.7);
});
}
});
tl.to(grp, { opacity: 0, duration: 0.14, ease: "power2.in" }, g.end - 0.14);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-particle-burst"] = tl;
})();
</script>
</body>
</html>
+311
View File
@@ -0,0 +1,311 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Particle Burst</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=Outfit:wght@900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0a0a;
}
#particle-burst {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#bs-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.bs-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1;
pointer-events: none;
}
#bs-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.bs-group {
position: absolute;
bottom: 120px;
left: 0;
width: 100%;
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: center;
gap: 24px;
padding: 0 100px;
opacity: 0;
visibility: hidden;
}
.bs-word {
font-family: "Outfit", sans-serif;
font-weight: 900;
font-size: 88px;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.45);
display: inline-block;
letter-spacing: 0.05em;
line-height: 1;
}
.bs-particle {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
opacity: 0;
pointer-events: none;
z-index: 20;
left: 960px;
bottom: 180px;
}
</style>
</head>
<body>
<div
id="particle-burst"
data-composition-id="caption-particle-burst"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="bs-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="bs-overlay"></div>
<div id="bs-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
function mulberry32(seed) {
return function () {
seed |= 0;
seed = (seed + 0x6d2b79f5) | 0;
var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
// Keywords get particle burst + gold color
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema.
var RAW_GROUPS = [
[0, 3],
[4, 7],
[8, 8],
[9, 12],
[13, 15],
[16, 16],
[17, 19],
[20, 22],
[23, 24],
[25, 27],
];
var GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0],
we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0;
var gEnd = Math.min(WORDS[we].end + 0.35, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
});
var PARTICLE_COLORS = [
"#FFD700",
"#FF6B35",
"#FF3C78",
"#9B5DE5",
"#00BBF9",
"#00F5D4",
"#ffffff",
"#F15BB5",
];
var NUM_PARTICLES = 10;
var container = document.getElementById("bs-container");
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
var groupText = groupWords
.map(function (w) {
return w.text.toUpperCase();
})
.join(" ");
var fontSize = fitFontSize(groupText, 88, "900", "'Outfit', sans-serif", 1720);
var grp = document.createElement("div");
grp.className = "bs-group";
grp.id = "bs-grp-" + gi;
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var span = document.createElement("span");
span.className = "bs-word";
span.id = "bs-w-" + wi;
span.textContent = w.text.toUpperCase();
span.style.fontSize = fontSize + "px";
grp.appendChild(span);
});
container.appendChild(grp);
});
// Create particles for keyword words
var particleData = {};
WORDS.forEach(function (w, wi) {
if (!KEYWORDS.has(wi)) return;
var rng = mulberry32(wi * 137 + 99);
particleData[wi] = [];
for (var p = 0; p < NUM_PARTICLES; p++) {
var angle = (p / NUM_PARTICLES) * Math.PI * 2 + rng() * 0.6;
var dist = 120 + rng() * 200;
var sz = 4 + rng() * 8;
var dx = Math.cos(angle) * dist;
var dy = -Math.sin(angle) * dist;
var dot = document.createElement("div");
dot.className = "bs-particle";
dot.id = "bs-p-" + wi + "-" + p;
dot.style.backgroundColor = PARTICLE_COLORS[p % PARTICLE_COLORS.length];
dot.style.width = sz.toFixed(1) + "px";
dot.style.height = sz.toFixed(1) + "px";
container.appendChild(dot);
particleData[wi].push({ el: dot, dx: dx, dy: dy });
}
});
GROUPS.forEach(function (g, gi) {
var grp = document.getElementById("bs-grp-" + gi);
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo(
grp,
{ opacity: 0, scale: 0.92 },
{ opacity: 1, scale: 1, duration: 0.2, ease: "back.out(1.5)" },
g.start,
);
groupWords.forEach(function (w, i) {
var wi = g.wordStart + i;
var wordEl = document.getElementById("bs-w-" + wi);
var isKW = KEYWORDS.has(wi);
tl.to(
wordEl,
{ color: isKW ? "#FFD700" : "#ffffff", scale: isKW ? 1.12 : 1.05, duration: 0.08 },
w.start,
);
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", scale: 1.0, duration: 0.1 }, w.end);
if (isKW && particleData[wi]) {
particleData[wi].forEach(function (pd, pi) {
tl.set(pd.el, { x: 0, y: 0, opacity: 0 }, w.start);
tl.to(
pd.el,
{ x: pd.dx, y: pd.dy, opacity: 1, duration: 0.12, ease: "power3.out" },
w.start + pi * 0.018,
);
tl.to(
pd.el,
{ opacity: 0, duration: 0.45, ease: "power1.in" },
w.start + 0.12 + pi * 0.018,
);
tl.set(pd.el, { opacity: 0, x: 0, y: 0 }, w.start + 0.7);
});
}
});
tl.to(grp, { opacity: 0, duration: 0.14, ease: "power2.in" }, g.end - 0.14);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
});
tl.seek(0);
window.__timelines["caption-particle-burst"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-particle-burst",
"type": "hyperframes:component",
"title": "Particle Burst",
"description": "Keyword words trigger colored particle explosions",
"tags": ["captions", "caption-style", "particles", "burst", "effects"],
"files": [
{
"path": "caption-particle-burst.html",
"target": "compositions/components/caption-particle-burst.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-particle-burst/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-particle-burst.png"
}
}
@@ -0,0 +1,401 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Pill Karaoke</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=Poppins:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Poppins", Arial, sans-serif;
}
#pill-karaoke {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1600px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1600px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
.caption-pill {
width: auto;
max-width: 1600px;
padding: 18px 60px 20px;
border-radius: 22px;
background: #e7e5e7;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
text-align: center;
overflow: visible;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
color: #a6a6a6;
font-family: "Poppins", Arial, sans-serif;
font-size: 72px;
font-weight: 700;
line-height: 1.16;
letter-spacing: 0;
white-space: nowrap;
word-break: normal;
overflow: visible;
}
.caption-line {
display: flex;
justify-content: center;
gap: 16px;
width: 100%;
max-width: none;
line-height: 1.16;
white-space: nowrap;
overflow: visible;
}
.caption-word {
display: inline-block;
color: #a6a6a6;
}
</style>
</head>
<body>
<div
id="pill-karaoke"
data-composition-id="caption-pill-karaoke"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var PILL_MAX_WIDTH = 1400;
var PILL_MIN_PADDING_X = 30;
var PILL_MAX_PADDING_X = 60;
var FONT_WIDTH_SAFETY = 1.18;
var FONT_FAMILY = "Poppins";
var FONT_WEIGHT = "700";
var BASE_FONT_SIZE = 72;
var MIN_FONT_SIZE = 52;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 16;
var PAUSE_THRESHOLD = 0.1;
var GROUP_END_BUFFER = 0.3;
var COLOR_INACTIVE = "#A6A6A6";
var COLOR_ACTIVE = "#1C1E1D";
var COLOR_FADE_DURATION = 0.1;
var WORD_FADE_LEAD = 0.05;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
var text = String(item.word || item.text || "").trim();
return {
text: text,
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (w) {
return w.text.length > 0;
});
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function measureWordWidth(text, fontSize) {
measureContext.font = FONT_WEIGHT + " " + fontSize + "px " + FONT_FAMILY;
return measureContext.measureText(text).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, fontSize) {
return words.reduce(function (total, word, index) {
return (
total + measureWordWidth(word.text.toLowerCase(), fontSize) + (index ? WORD_SPACING : 0)
);
}, 0);
}
function splitLinesForGroup(words, fontSize) {
var maxLineWidth = PILL_MAX_WIDTH - PILL_MIN_PADDING_X * 2;
if (
words.some(function (w) {
return measureWordWidth(w.text.toLowerCase(), fontSize) > maxLineWidth;
})
) {
return null;
}
if (measureLineWidth(words, fontSize) <= maxLineWidth) return [words];
var bestSplit = null;
var bestImbalance = Infinity;
for (var split = 1; split < words.length; split++) {
var firstLine = words.slice(0, split);
var secondLine = words.slice(split);
var firstWidth = measureLineWidth(firstLine, fontSize);
var secondWidth = measureLineWidth(secondLine, fontSize);
if (firstWidth <= maxLineWidth && secondWidth <= maxLineWidth) {
var imbalance = Math.abs(firstWidth - secondWidth);
if (imbalance < bestImbalance) {
bestSplit = [firstLine, secondLine];
bestImbalance = imbalance;
}
}
}
return bestSplit;
}
function fitsInTwoLinesAtSize(words, fontSize) {
return splitLinesForGroup(words, fontSize) !== null;
}
function planPillLayout(words, fontSize) {
var lines = splitLinesForGroup(words, fontSize) || [words];
var widestLine = lines.reduce(function (max, lineWords) {
return Math.max(max, measureLineWidth(lineWords, fontSize));
}, 0);
var availablePadding = Math.floor((PILL_MAX_WIDTH - widestLine) / 2);
var padX = Math.max(PILL_MIN_PADDING_X, Math.min(PILL_MAX_PADDING_X, availablePadding));
var pillWidth = Math.min(PILL_MAX_WIDTH, Math.ceil(widestLine + padX * 2));
return { lines: lines, padX: padX, pillWidth: pillWidth };
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var testGroup = current.concat([word]);
var nextWord = words[index + 1];
var wouldExceedMaxWords = testGroup.length > MAX_WORDS_PER_GROUP;
var wouldExceedTwoLines = !fitsInTwoLinesAtSize(testGroup, BASE_FONT_SIZE);
if (wouldExceedMaxWords || wouldExceedTwoLines) {
if (current.length) {
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
}
current = [word];
return;
}
current.push(word);
var hasPunctuation = /[,.:!?]$/.test(word.text);
var pauseDuration = nextWord ? nextWord.start - word.end : 0;
var hasNaturalPause = pauseDuration >= PAUSE_THRESHOLD;
var maxWordsReached = current.length >= MAX_WORDS_PER_GROUP;
if (maxWordsReached || !nextWord || hasPunctuation || hasNaturalPause) {
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
current = [];
}
});
if (current.length)
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
return groups;
}
function fontSizeForGroup(words) {
var size = BASE_FONT_SIZE;
while (size > MIN_FONT_SIZE && !fitsInTwoLinesAtSize(words, size)) {
size -= 2;
}
return size;
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupEl = document.createElement("div");
var pillEl = document.createElement("div");
var copyEl = document.createElement("div");
var fontSize = fontSizeForGroup(group.words);
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
pillEl.className = "caption-pill";
copyEl.className = "caption-copy";
copyEl.style.fontSize = fontSize + "px";
var indexedWords = group.words.map(function (word, wordIndex) {
return { text: word.text, start: word.start, end: word.end, wordIndex: wordIndex };
});
var layout = planPillLayout(indexedWords, fontSize);
pillEl.style.width = layout.pillWidth + "px";
pillEl.style.paddingLeft = layout.padX + "px";
pillEl.style.paddingRight = layout.padX + "px";
layout.lines.slice(0, 2).forEach(function (lineWords) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineWords.forEach(function (word) {
var wordEl = document.createElement("span");
wordEl.id = "caption-word-" + groupIndex + "-" + word.wordIndex;
wordEl.className = "caption-word";
wordEl.textContent = word.text.toLowerCase();
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
pillEl.appendChild(copyEl);
groupEl.appendChild(pillEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (group, groupIndex) {
var groupEl = document.getElementById("caption-group-" + groupIndex);
var nextGroup = GROUPS[groupIndex + 1];
var isLastGroup = groupIndex === GROUPS.length - 1;
var effectiveEnd = isLastGroup
? DURATION
: Math.min(nextGroup.start, group.end + GROUP_END_BUFFER);
var visibleStart = Math.max(0, group.start);
var visibleEnd = Math.max(visibleStart + 0.01, effectiveEnd);
tl.set(groupEl, { opacity: 1 }, visibleStart);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
group.words.forEach(function (word, wordIndex) {
var wordEl = document.getElementById("caption-word-" + groupIndex + "-" + wordIndex);
var isFirstWord = wordIndex === 0;
var wordStart = Math.max(visibleStart, word.start - WORD_FADE_LEAD);
tl.set(wordEl, { color: isFirstWord ? COLOR_ACTIVE : COLOR_INACTIVE }, visibleStart);
if (isFirstWord) return;
tl.to(
wordEl,
{ color: COLOR_ACTIVE, duration: COLOR_FADE_DURATION, ease: "none" },
wordStart,
);
});
});
window.__timelines["caption-pill-karaoke"] = tl;
</script>
</body>
</html>
+399
View File
@@ -0,0 +1,399 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Pill Karaoke</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=Poppins:wght@700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000000;
font-family: "Poppins", Arial, sans-serif;
}
#pill-karaoke {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000000;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1600px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1600px;
height: 220px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
.caption-pill {
width: auto;
max-width: 1600px;
padding: 18px 60px 20px;
border-radius: 22px;
background: #e7e5e7;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
text-align: center;
overflow: visible;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
color: #a6a6a6;
font-family: "Poppins", Arial, sans-serif;
font-size: 72px;
font-weight: 700;
line-height: 1.16;
letter-spacing: 0;
white-space: nowrap;
word-break: normal;
overflow: visible;
}
.caption-line {
display: flex;
justify-content: center;
gap: 16px;
width: 100%;
max-width: none;
line-height: 1.16;
white-space: nowrap;
overflow: visible;
}
.caption-word {
display: inline-block;
color: #a6a6a6;
}
</style>
</head>
<body>
<div
id="pill-karaoke"
data-composition-id="caption-pill-karaoke"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var PILL_MAX_WIDTH = 1400;
var PILL_MIN_PADDING_X = 30;
var PILL_MAX_PADDING_X = 60;
var FONT_WIDTH_SAFETY = 1.18;
var FONT_FAMILY = "Poppins";
var FONT_WEIGHT = "700";
var BASE_FONT_SIZE = 72;
var MIN_FONT_SIZE = 52;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 16;
var PAUSE_THRESHOLD = 0.1;
var GROUP_END_BUFFER = 0.3;
var COLOR_INACTIVE = "#A6A6A6";
var COLOR_ACTIVE = "#1C1E1D";
var COLOR_FADE_DURATION = 0.1;
var WORD_FADE_LEAD = 0.05;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
var text = String(item.word || item.text || "").trim();
return {
text: text,
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (w) {
return w.text.length > 0;
});
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
function measureWordWidth(text, fontSize) {
measureContext.font = FONT_WEIGHT + " " + fontSize + "px " + FONT_FAMILY;
return measureContext.measureText(text).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words, fontSize) {
return words.reduce(function (total, word, index) {
return (
total + measureWordWidth(word.text.toLowerCase(), fontSize) + (index ? WORD_SPACING : 0)
);
}, 0);
}
function splitLinesForGroup(words, fontSize) {
var maxLineWidth = PILL_MAX_WIDTH - PILL_MIN_PADDING_X * 2;
if (
words.some(function (w) {
return measureWordWidth(w.text.toLowerCase(), fontSize) > maxLineWidth;
})
) {
return null;
}
if (measureLineWidth(words, fontSize) <= maxLineWidth) return [words];
var bestSplit = null;
var bestImbalance = Infinity;
for (var split = 1; split < words.length; split++) {
var firstLine = words.slice(0, split);
var secondLine = words.slice(split);
var firstWidth = measureLineWidth(firstLine, fontSize);
var secondWidth = measureLineWidth(secondLine, fontSize);
if (firstWidth <= maxLineWidth && secondWidth <= maxLineWidth) {
var imbalance = Math.abs(firstWidth - secondWidth);
if (imbalance < bestImbalance) {
bestSplit = [firstLine, secondLine];
bestImbalance = imbalance;
}
}
}
return bestSplit;
}
function fitsInTwoLinesAtSize(words, fontSize) {
return splitLinesForGroup(words, fontSize) !== null;
}
function planPillLayout(words, fontSize) {
var lines = splitLinesForGroup(words, fontSize) || [words];
var widestLine = lines.reduce(function (max, lineWords) {
return Math.max(max, measureLineWidth(lineWords, fontSize));
}, 0);
var availablePadding = Math.floor((PILL_MAX_WIDTH - widestLine) / 2);
var padX = Math.max(PILL_MIN_PADDING_X, Math.min(PILL_MAX_PADDING_X, availablePadding));
var pillWidth = Math.min(PILL_MAX_WIDTH, Math.ceil(widestLine + padX * 2));
return { lines: lines, padX: padX, pillWidth: pillWidth };
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var testGroup = current.concat([word]);
var nextWord = words[index + 1];
var wouldExceedMaxWords = testGroup.length > MAX_WORDS_PER_GROUP;
var wouldExceedTwoLines = !fitsInTwoLinesAtSize(testGroup, BASE_FONT_SIZE);
if (wouldExceedMaxWords || wouldExceedTwoLines) {
if (current.length) {
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
}
current = [word];
return;
}
current.push(word);
var hasPunctuation = /[,.:!?]$/.test(word.text);
var pauseDuration = nextWord ? nextWord.start - word.end : 0;
var hasNaturalPause = pauseDuration >= PAUSE_THRESHOLD;
var maxWordsReached = current.length >= MAX_WORDS_PER_GROUP;
if (maxWordsReached || !nextWord || hasPunctuation || hasNaturalPause) {
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
current = [];
}
});
if (current.length)
groups.push({
words: current.slice(),
start: current[0].start,
end: current[current.length - 1].end,
});
return groups;
}
function fontSizeForGroup(words) {
var size = BASE_FONT_SIZE;
while (size > MIN_FONT_SIZE && !fitsInTwoLinesAtSize(words, size)) {
size -= 2;
}
return size;
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupEl = document.createElement("div");
var pillEl = document.createElement("div");
var copyEl = document.createElement("div");
var fontSize = fontSizeForGroup(group.words);
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
pillEl.className = "caption-pill";
copyEl.className = "caption-copy";
copyEl.style.fontSize = fontSize + "px";
var indexedWords = group.words.map(function (word, wordIndex) {
return { text: word.text, start: word.start, end: word.end, wordIndex: wordIndex };
});
var layout = planPillLayout(indexedWords, fontSize);
pillEl.style.width = layout.pillWidth + "px";
pillEl.style.paddingLeft = layout.padX + "px";
pillEl.style.paddingRight = layout.padX + "px";
layout.lines.slice(0, 2).forEach(function (lineWords) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineWords.forEach(function (word) {
var wordEl = document.createElement("span");
wordEl.id = "caption-word-" + groupIndex + "-" + word.wordIndex;
wordEl.className = "caption-word";
wordEl.textContent = word.text.toLowerCase();
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
pillEl.appendChild(copyEl);
groupEl.appendChild(pillEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (group, groupIndex) {
var groupEl = document.getElementById("caption-group-" + groupIndex);
var nextGroup = GROUPS[groupIndex + 1];
var isLastGroup = groupIndex === GROUPS.length - 1;
var effectiveEnd = isLastGroup
? DURATION
: Math.min(nextGroup.start, group.end + GROUP_END_BUFFER);
var visibleStart = Math.max(0, group.start);
var visibleEnd = Math.max(visibleStart + 0.01, effectiveEnd);
tl.set(groupEl, { opacity: 1 }, visibleStart);
tl.set(groupEl, { opacity: 0 }, visibleEnd);
group.words.forEach(function (word, wordIndex) {
var wordEl = document.getElementById("caption-word-" + groupIndex + "-" + wordIndex);
var isFirstWord = wordIndex === 0;
var wordStart = Math.max(visibleStart, word.start - WORD_FADE_LEAD);
tl.set(wordEl, { color: isFirstWord ? COLOR_ACTIVE : COLOR_INACTIVE }, visibleStart);
if (isFirstWord) return;
tl.to(
wordEl,
{ color: COLOR_ACTIVE, duration: COLOR_FADE_DURATION, ease: "none" },
wordStart,
);
});
});
window.__timelines["caption-pill-karaoke"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-pill-karaoke",
"type": "hyperframes:component",
"title": "Pill Karaoke",
"description": "Pill-shaped container with per-word karaoke color highlight",
"tags": ["captions", "caption-style", "karaoke", "pill"],
"files": [
{
"path": "caption-pill-karaoke.html",
"target": "compositions/components/caption-pill-karaoke.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-pill-karaoke/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-pill-karaoke.png"
}
}
+227
View File
@@ -0,0 +1,227 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Texture</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=Anton&display=swap" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
.hf-texture-text {
color: #fff;
-webkit-mask-size: var(--mask-size, cover);
mask-size: var(--mask-size, cover);
-webkit-mask-position: var(--mask-position, center);
mask-position: var(--mask-position, center);
-webkit-mask-mode: luminance;
mask-mode: luminance;
filter: contrast(var(--mask-contrast, 1));
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
}
#texture-lava {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#lv-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.lv-overlay {
position: absolute;
inset: 0;
background: transparent;
z-index: 1;
pointer-events: none;
}
#lv-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.lv-texture-override {
-webkit-mask-size: 200% 200%;
mask-size: 200% 200%;
-webkit-mask-position: 0% 50%;
mask-position: 0% 50%;
-webkit-mask-mode: luminance;
mask-mode: luminance;
filter: contrast(1.2);
}
.lv-shadow {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 50%;
filter: drop-shadow(0 4px 24px rgba(255, 100, 20, 0.55));
opacity: 0;
visibility: hidden;
}
.lv-word {
font-family: "Anton", sans-serif;
font-weight: 400;
text-transform: uppercase;
color: #ffd0a0;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
}
</style>
</head>
<body>
<div
id="caption-texture"
data-composition-id="caption-texture"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
data-composition-variables='{"texture":{"type":"string","default":"lava"}}'
>
<video
id="lv-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="lv-overlay"></div>
<div id="lv-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
var TEXTURE = vars.texture || "lava";
var TEXTURE_URL = "compositions/components/" + TEXTURE + ".png";
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]);
var container = document.getElementById("lv-container");
var tl = gsap.timeline({ paused: true });
WORDS.forEach(function (w, wi) {
var txt = w.text.toUpperCase();
var fontSize = fitFontSize(txt, 200, "400", "'Anton', sans-serif", 1720);
var shadow = document.createElement("div");
shadow.className = "lv-shadow";
shadow.id = "lv-shadow-" + wi;
gsap.set(shadow, { yPercent: -50 });
var wordEl = document.createElement("div");
wordEl.className = "hf-texture-text lv-texture-override lv-word";
wordEl.id = "lv-word-" + wi;
wordEl.textContent = txt;
wordEl.style.fontSize = fontSize + "px";
wordEl.style.webkitMaskImage = "url('" + TEXTURE_URL + "')";
wordEl.style.maskImage = "url('" + TEXTURE_URL + "')";
if (KEYWORDS.has(wi)) wordEl.style.color = "#FFAA44";
shadow.appendChild(wordEl);
container.appendChild(shadow);
var dur = Math.max(w.end - w.start + 0.3, 0.4);
tl.set(shadow, { visibility: "visible", opacity: 0 }, w.start);
tl.fromTo(
shadow,
{ opacity: 0, scale: 0.88 },
{ opacity: 1, scale: 1, duration: 0.18, ease: "power3.out" },
w.start,
);
tl.set(wordEl, { "--mask-position": "0% 50%" }, w.start);
tl.to(
wordEl,
{ "--mask-position": "100% 50%", duration: dur, ease: "sine.inOut" },
w.start,
);
tl.to(shadow, { opacity: 0, scale: 1.08, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(shadow, { opacity: 0, visibility: "hidden" }, w.end + 0.1);
});
tl.seek(0);
window.__timelines["caption-texture"] = tl;
})();
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

+226
View File
@@ -0,0 +1,226 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Texture</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=Anton&display=swap" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
.hf-texture-text {
color: #fff;
-webkit-mask-size: var(--mask-size, cover);
mask-size: var(--mask-size, cover);
-webkit-mask-position: var(--mask-position, center);
mask-position: var(--mask-position, center);
-webkit-mask-mode: luminance;
mask-mode: luminance;
filter: contrast(var(--mask-contrast, 1));
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #0a0805;
}
#texture-lava {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0805;
}
#lv-video {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.lv-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 1;
pointer-events: none;
}
#lv-container {
position: absolute;
inset: 0;
z-index: 10;
pointer-events: none;
}
.lv-texture-override {
-webkit-mask-size: 200% 200%;
mask-size: 200% 200%;
-webkit-mask-position: 0% 50%;
mask-position: 0% 50%;
-webkit-mask-mode: luminance;
mask-mode: luminance;
filter: contrast(1.2);
}
.lv-shadow {
position: absolute;
left: 0;
width: 100%;
text-align: center;
top: 50%;
filter: drop-shadow(0 4px 24px rgba(255, 100, 20, 0.55));
opacity: 0;
visibility: hidden;
}
.lv-word {
font-family: "Anton", sans-serif;
font-weight: 400;
text-transform: uppercase;
color: #ffd0a0;
display: inline-block;
letter-spacing: 0.04em;
line-height: 1;
}
</style>
</head>
<body>
<div
id="caption-texture"
data-composition-id="caption-texture"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
data-composition-variables='{"texture":{"type":"string","default":"lava"}}'
>
<video
id="lv-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="lv-overlay"></div>
<div id="lv-container"></div>
</div>
<script>
(function () {
window.__timelines = window.__timelines || {};
var vars =
window.__hyperframes && window.__hyperframes.getVariables
? window.__hyperframes.getVariables()
: {};
var TEXTURE = vars.texture || "lava";
var TEXTURE_URL = "compositions/components/" + TEXTURE + ".png";
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var WORDS = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
var KEYWORDS = new Set([8, 12, 15, 24, 27]);
var container = document.getElementById("lv-container");
var tl = gsap.timeline({ paused: true });
WORDS.forEach(function (w, wi) {
var txt = w.text.toUpperCase();
var fontSize = fitFontSize(txt, 200, "400", "'Anton', sans-serif", 1720);
var shadow = document.createElement("div");
shadow.className = "lv-shadow";
shadow.id = "lv-shadow-" + wi;
gsap.set(shadow, { yPercent: -50 });
var wordEl = document.createElement("div");
wordEl.className = "hf-texture-text lv-texture-override lv-word";
wordEl.id = "lv-word-" + wi;
wordEl.textContent = txt;
wordEl.style.fontSize = fontSize + "px";
wordEl.style.webkitMaskImage = "url('" + TEXTURE_URL + "')";
wordEl.style.maskImage = "url('" + TEXTURE_URL + "')";
if (KEYWORDS.has(wi)) wordEl.style.color = "#FFAA44";
shadow.appendChild(wordEl);
container.appendChild(shadow);
var dur = Math.max(w.end - w.start + 0.3, 0.4);
tl.set(shadow, { visibility: "visible", opacity: 0 }, w.start);
tl.fromTo(
shadow,
{ opacity: 0, scale: 0.88 },
{ opacity: 1, scale: 1, duration: 0.18, ease: "power3.out" },
w.start,
);
tl.set(wordEl, { "--mask-position": "0% 50%" }, w.start);
tl.to(
wordEl,
{ "--mask-position": "100% 50%", duration: dur, ease: "sine.inOut" },
w.start,
);
tl.to(shadow, { opacity: 0, scale: 1.08, duration: 0.1, ease: "power2.in" }, w.end);
tl.set(shadow, { opacity: 0, visibility: "hidden" }, w.end + 0.1);
});
tl.seek(0);
window.__timelines["caption-texture"] = tl;
})();
</script>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

@@ -0,0 +1,49 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-texture",
"type": "hyperframes:component",
"title": "Texture",
"description": "Flowing texture mask over large uppercase text — ships with 6 textures (lava, marble, metal, wood, concrete, rock), configurable via the texture variable",
"tags": ["captions", "caption-style", "texture", "mask"],
"files": [
{
"path": "caption-texture.html",
"target": "compositions/components/caption-texture.html",
"type": "hyperframes:snippet"
},
{
"path": "lava.png",
"target": "compositions/components/lava.png",
"type": "hyperframes:asset"
},
{
"path": "marble-012.png",
"target": "compositions/components/marble-012.png",
"type": "hyperframes:asset"
},
{
"path": "metal-046-b.png",
"target": "compositions/components/metal-046-b.png",
"type": "hyperframes:asset"
},
{
"path": "wood-066.png",
"target": "compositions/components/wood-066.png",
"type": "hyperframes:asset"
},
{
"path": "concrete-042-a.png",
"target": "compositions/components/concrete-042-a.png",
"type": "hyperframes:asset"
},
{
"path": "rock-058.png",
"target": "compositions/components/rock-058.png",
"type": "hyperframes:asset"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-texture-lava/preview.mp4?v=1779051416",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-texture.png"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

@@ -0,0 +1,416 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Weight Shift</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=Montserrat:wght@300;700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: transparent;
font-family: "Montserrat", Arial, sans-serif;
}
#weight-shift {
pointer-events: none;
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: transparent;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 1400px;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 300;
font-size: 72px;
letter-spacing: -0.03em;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
text-transform: lowercase;
}
</style>
</head>
<body>
<div
id="weight-shift"
data-composition-id="caption-weight-shift"
data-timeline-locked
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var MAX_LINE_WIDTH = SAFE_ZONE_WIDTH - 40;
var CAPTION_FONT_SIZE = 72;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 12;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 100 / 1000;
var WEIGHT_TRANSITION = 100 / 1000;
var FONT_WIDTH_SAFETY = 1.14;
var FONT_WEIGHT_LIGHT = 300;
var FONT_WEIGHT_BOLD = 700;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
return {
text: String(item.word || item.text || "").trim(),
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
measureContext.font = "700 " + CAPTION_FONT_SIZE + "px Montserrat";
function measureWord(word) {
return measureContext.measureText(word.text.toLowerCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words) {
return words.reduce(function (total, word, index) {
return total + measureWord(word) + (index ? WORD_SPACING : 0);
}, 0);
}
function fitsInTwoLines(words) {
return measureLineWidth(words) <= MAX_LINE_WIDTH * 1.8;
}
function splitLines(words) {
if (words.length < 2) {
return [{ words: words, startIndex: 0 }];
}
var bestSplit = Math.ceil(words.length / 2);
var bestDiff = Infinity;
for (var s = 1; s < words.length; s++) {
var w1 = measureLineWidth(words.slice(0, s));
var w2 = measureLineWidth(words.slice(s));
if (w1 <= MAX_LINE_WIDTH && w2 <= MAX_LINE_WIDTH) {
var diff = Math.abs(w1 - w2);
if (diff < bestDiff) {
bestDiff = diff;
bestSplit = s;
}
}
}
var result = [];
var line1 = words.slice(0, bestSplit);
var line2 = words.slice(bestSplit);
if (line1.length) result.push({ words: line1, startIndex: 0 });
if (line2.length) result.push({ words: line2, startIndex: line1.length });
return result;
}
function avoidSingleWordGroups(groups) {
var out = [];
groups.forEach(function (group) {
if (
group.words.length === 1 &&
out.length > 0 &&
out[out.length - 1].words.length < MAX_WORDS_PER_GROUP
) {
out[out.length - 1] = makeGroup(out[out.length - 1].words.concat(group.words));
} else {
out.push(group);
}
});
for (var i = 0; i < out.length; i++) {
if (out[i].words.length !== 1) continue;
if (i + 1 < out.length && out[i + 1].words.length < MAX_WORDS_PER_GROUP) {
out[i] = makeGroup(out[i].words.concat(out[i + 1].words));
out.splice(i + 1, 1);
} else if (i > 0 && out[i - 1].words.length < MAX_WORDS_PER_GROUP) {
out[i - 1] = makeGroup(out[i - 1].words.concat(out[i].words));
out.splice(i, 1);
i--;
}
}
return out;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
if (testGroup.length > MAX_WORDS_PER_GROUP || !fitsInTwoLines(testGroup)) {
if (current.length) groups.push(makeGroup(current));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current));
current = [];
}
});
if (current.length) groups.push(makeGroup(current));
return avoidSingleWordGroups(groups);
}
function makeGroup(words) {
return {
words: words.slice(),
lines: splitLines(words),
start: words[0].start,
end: words[words.length - 1].end,
};
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupText = group.words
.map(function (w) {
return w.text.toLowerCase();
})
.join(" ");
var computedSize = fitFontSize(
groupText,
CAPTION_FONT_SIZE,
"700",
"Montserrat",
MAX_LINE_WIDTH,
);
var groupEl = document.createElement("div");
var copyEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
copyEl.className = "caption-copy";
group.lines.forEach(function (line, lineIndex) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span");
wordEl.textContent = word.text.toLowerCase();
wordEl.style.display = "inline-block";
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
groupEl.appendChild(copyEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scale: 0.85 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scale: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
if (group.lines.length === 2) {
var line1El = document.getElementById("caption-group-" + groupIndex + "-line-0");
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start;
tl.to(
line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
tl.to(
line2El,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
}
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-weight-shift"] = tl;
</script>
</body>
</html>
+414
View File
@@ -0,0 +1,414 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Weight Shift</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=Montserrat:wght@300;700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
margin: 0;
overflow: hidden;
background: #000000;
font-family: "Montserrat", Arial, sans-serif;
}
#weight-shift {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #000000;
}
#avatar-video {
position: absolute;
inset: 0;
z-index: 1;
width: 100%;
height: 100%;
object-fit: cover;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
transform: translateZ(0);
}
.safe-zone {
position: absolute;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
position: absolute;
left: 0;
top: 0;
width: 1400px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform-origin: 50% 50%;
will-change: transform;
backface-visibility: hidden;
}
.caption-copy {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-width: 1400px;
text-align: center;
}
.caption-line {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
width: max-content;
max-width: 1400px;
line-height: 1.1;
white-space: nowrap;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 300;
font-size: 72px;
letter-spacing: -0.03em;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
text-transform: lowercase;
}
</style>
</head>
<body>
<div
id="weight-shift"
data-composition-id="caption-weight-shift"
data-start="0"
data-duration="8"
data-fps="30"
data-width="1920"
data-height="1080"
>
<video
id="avatar-video"
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="safe-zone"></div>
</div>
</div>
<script>
var DURATION = 8;
var SAFE_ZONE_WIDTH = 1400;
var MAX_LINE_WIDTH = SAFE_ZONE_WIDTH - 40;
var CAPTION_FONT_SIZE = 72;
var MAX_WORDS_PER_GROUP = 4;
var WORD_SPACING = 12;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 100 / 1000;
var WEIGHT_TRANSITION = 100 / 1000;
var FONT_WIDTH_SAFETY = 1.14;
var FONT_WEIGHT_LIGHT = 300;
var FONT_WEIGHT_BOLD = 700;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 },
{ text: "starts", start: 0.85, end: 1.1 },
{ text: "with", start: 1.1, end: 1.25 },
{ text: "a", start: 1.25, end: 1.35 },
{ text: "single", start: 1.35, end: 1.65 },
{ text: "frame.", start: 1.65, end: 2.0 },
{ text: "HyperFrames", start: 2.1, end: 2.65 },
{ text: "lets", start: 2.65, end: 2.85 },
{ text: "you", start: 2.85, end: 2.95 },
{ text: "write", start: 2.95, end: 3.15 },
{ text: "HTML", start: 3.15, end: 3.55 },
{ text: "and", start: 3.55, end: 3.65 },
{ text: "render", start: 3.65, end: 3.95 },
{ text: "professional", start: 3.95, end: 4.45 },
{ text: "video.", start: 4.45, end: 4.8 },
{ text: "No", start: 4.9, end: 5.05 },
{ text: "timeline.", start: 5.05, end: 5.45 },
{ text: "No", start: 5.5, end: 5.65 },
{ text: "drag", start: 5.65, end: 5.85 },
{ text: "and", start: 5.85, end: 5.95 },
{ text: "drop.", start: 5.95, end: 6.25 },
{ text: "Just", start: 6.35, end: 6.55 },
{ text: "code", start: 6.55, end: 6.8 },
{ text: "that", start: 6.8, end: 6.95 },
{ text: "becomes", start: 6.95, end: 7.3 },
{ text: "cinema.", start: 7.3, end: 7.7 },
];
function normalizeWords(rawWords) {
return rawWords
.map(function (item) {
return {
text: String(item.word || item.text || "").trim(),
start: Math.max(0, Number(item.start) || 0),
end: Math.min(DURATION, Math.max(Number(item.start) || 0, Number(item.end) || 0)),
};
})
.filter(function (word) {
return word.text.length > 0;
});
}
var _fitCanvas = document.createElement("canvas");
var _fitCtx = _fitCanvas.getContext("2d");
function fitFontSize(text, baseFontSize, fontWeight, fontFamily, maxWidth) {
var size = baseFontSize;
var minSize = Math.floor(baseFontSize * 0.45);
while (size > minSize) {
_fitCtx.font = fontWeight + " " + size + "px " + fontFamily;
if (_fitCtx.measureText(text).width <= maxWidth) return size;
size -= 2;
}
return minSize;
}
var measureCanvas = document.createElement("canvas");
var measureContext = measureCanvas.getContext("2d");
measureContext.font = "700 " + CAPTION_FONT_SIZE + "px Montserrat";
function measureWord(word) {
return measureContext.measureText(word.text.toLowerCase()).width * FONT_WIDTH_SAFETY;
}
function measureLineWidth(words) {
return words.reduce(function (total, word, index) {
return total + measureWord(word) + (index ? WORD_SPACING : 0);
}, 0);
}
function fitsInTwoLines(words) {
return measureLineWidth(words) <= MAX_LINE_WIDTH * 1.8;
}
function splitLines(words) {
if (words.length < 2) {
return [{ words: words, startIndex: 0 }];
}
var bestSplit = Math.ceil(words.length / 2);
var bestDiff = Infinity;
for (var s = 1; s < words.length; s++) {
var w1 = measureLineWidth(words.slice(0, s));
var w2 = measureLineWidth(words.slice(s));
if (w1 <= MAX_LINE_WIDTH && w2 <= MAX_LINE_WIDTH) {
var diff = Math.abs(w1 - w2);
if (diff < bestDiff) {
bestDiff = diff;
bestSplit = s;
}
}
}
var result = [];
var line1 = words.slice(0, bestSplit);
var line2 = words.slice(bestSplit);
if (line1.length) result.push({ words: line1, startIndex: 0 });
if (line2.length) result.push({ words: line2, startIndex: line1.length });
return result;
}
function avoidSingleWordGroups(groups) {
var out = [];
groups.forEach(function (group) {
if (
group.words.length === 1 &&
out.length > 0 &&
out[out.length - 1].words.length < MAX_WORDS_PER_GROUP
) {
out[out.length - 1] = makeGroup(out[out.length - 1].words.concat(group.words));
} else {
out.push(group);
}
});
for (var i = 0; i < out.length; i++) {
if (out[i].words.length !== 1) continue;
if (i + 1 < out.length && out[i + 1].words.length < MAX_WORDS_PER_GROUP) {
out[i] = makeGroup(out[i].words.concat(out[i + 1].words));
out.splice(i + 1, 1);
} else if (i > 0 && out[i - 1].words.length < MAX_WORDS_PER_GROUP) {
out[i - 1] = makeGroup(out[i - 1].words.concat(out[i].words));
out.splice(i, 1);
i--;
}
}
return out;
}
function makeGroups(words) {
var groups = [];
var current = [];
words.forEach(function (word, index) {
var nextWord = words[index + 1];
var testGroup = current.concat([word]);
if (testGroup.length > MAX_WORDS_PER_GROUP || !fitsInTwoLines(testGroup)) {
if (current.length) groups.push(makeGroup(current));
current = [word];
return;
}
current.push(word);
var punctuation = /[,.:!?]$/.test(word.text);
var pause = nextWord ? nextWord.start - word.end : 0;
if (
!nextWord ||
punctuation ||
pause >= PAUSE_THRESHOLD ||
current.length >= MAX_WORDS_PER_GROUP
) {
groups.push(makeGroup(current));
current = [];
}
});
if (current.length) groups.push(makeGroup(current));
return avoidSingleWordGroups(groups);
}
function makeGroup(words) {
return {
words: words.slice(),
lines: splitLines(words),
start: words[0].start,
end: words[words.length - 1].end,
};
}
function buildCaptions(groups) {
var stage = document.getElementById("caption-stage");
groups.forEach(function (group, groupIndex) {
var groupText = group.words
.map(function (w) {
return w.text.toLowerCase();
})
.join(" ");
var computedSize = fitFontSize(
groupText,
CAPTION_FONT_SIZE,
"700",
"Montserrat",
MAX_LINE_WIDTH,
);
var groupEl = document.createElement("div");
var copyEl = document.createElement("div");
groupEl.id = "caption-group-" + groupIndex;
groupEl.className = "caption-group";
copyEl.className = "caption-copy";
group.lines.forEach(function (line, lineIndex) {
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span");
wordEl.textContent = word.text.toLowerCase();
wordEl.style.display = "inline-block";
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
});
groupEl.appendChild(copyEl);
stage.appendChild(groupEl);
});
}
var WORDS = normalizeWords(TRANSCRIPT);
var GROUPS = makeGroups(WORDS);
buildCaptions(GROUPS);
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
var visibleStarts = GROUPS.map(function (group, groupIndex) {
var prev = GROUPS[groupIndex - 1];
var availableGap = prev ? Math.max(0, group.start - prev.end) : ENTRY_DURATION;
var entryLead = Math.min(ENTRY_DURATION, availableGap);
return Math.max(0, group.start - entryLead);
});
var allGroupEls = GROUPS.map(function (_, i) {
return document.getElementById("caption-group-" + i);
});
GROUPS.forEach(function (group, groupIndex) {
var groupEl = allGroupEls[groupIndex];
var visibleStart = visibleStarts[groupIndex];
var visibleEnd = groupIndex < GROUPS.length - 1 ? visibleStarts[groupIndex + 1] : DURATION;
allGroupEls.forEach(function (otherEl, otherIndex) {
if (otherIndex !== groupIndex) tl.set(otherEl, { opacity: 0 }, visibleStart);
});
tl.set(groupEl, { opacity: 0, scale: 0.85 }, visibleStart);
tl.to(
groupEl,
{ opacity: 1, scale: 1, duration: ENTRY_DURATION, ease: "power3.out", force3D: true },
visibleStart,
);
if (group.lines.length === 2) {
var line1El = document.getElementById("caption-group-" + groupIndex + "-line-0");
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start;
tl.to(
line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
tl.to(
line2El,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
}
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
window.__timelines["caption-weight-shift"] = tl;
</script>
</body>
</html>
@@ -0,0 +1,19 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-weight-shift",
"type": "hyperframes:component",
"title": "Weight Shift",
"description": "Elegant font-weight transition between caption lines",
"tags": ["captions", "caption-style", "minimal", "typography"],
"files": [
{
"path": "caption-weight-shift.html",
"target": "compositions/components/caption-weight-shift.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-weight-shift/preview-v2.mp4",
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-weight-shift.png"
}
}
+155
View File
@@ -0,0 +1,155 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Grain Overlay — Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="grain-overlay-demo"
data-width="1920"
data-height="1080"
data-duration="5"
>
<!-- Background content to demonstrate the overlay -->
<div class="demo-bg">
<div style="text-align: center; z-index: 1">
<div
class="demo-title"
style="font-size: 80px; font-weight: 700; color: #2c1e16; opacity: 0"
>
Grain Overlay
</div>
<div
class="demo-subtitle"
style="font-size: 32px; color: #5a4a3a; margin-top: 20px; opacity: 0"
>
Adds warmth and analog character
</div>
</div>
</div>
<!-- The grain overlay component -->
<div
id="grain-overlay"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
"
>
<div class="grain-texture"></div>
</div>
<style>
.demo-bg {
width: 1920px;
height: 1080px;
background: linear-gradient(135deg, #f5f0e0 0%, #e8d8b8 50%, #d4c4a0 100%);
display: flex;
justify-content: center;
align-items: center;
font-family: "Inter", sans-serif;
position: relative;
}
@keyframes hf-grain-noise {
0%,
100% {
transform: translate(0, 0);
}
10% {
transform: translate(-5%, -5%);
}
20% {
transform: translate(-10%, 5%);
}
30% {
transform: translate(5%, -10%);
}
40% {
transform: translate(-5%, 15%);
}
50% {
transform: translate(-10%, 5%);
}
60% {
transform: translate(15%, 0);
}
70% {
transform: translate(0, 10%);
}
80% {
transform: translate(-15%, 0);
}
90% {
transform: translate(10%, 5%);
}
}
#grain-overlay .grain-texture {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.15;
animation: hf-grain-noise 0.5s steps(1) infinite;
}
</style>
<script>
(function () {
const tl = gsap.timeline({ paused: true });
tl.to(
".demo-title",
{
opacity: 1,
y: -10,
duration: 1,
ease: "power3.out",
},
0.5,
);
tl.to(
".demo-subtitle",
{
opacity: 1,
y: -5,
duration: 0.8,
ease: "power3.out",
},
1.0,
);
window.__timelines = window.__timelines || {};
window.__timelines["grain-overlay-demo"] = tl;
})();
</script>
</div>
</body>
</html>
+75
View File
@@ -0,0 +1,75 @@
<!--
Grain Overlay — animated film grain texture.
Usage: paste this snippet into your composition's HTML.
The overlay covers the full viewport with pointer-events: none
so it sits on top of all content without blocking interaction.
Customize:
- opacity: adjust .grain-texture opacity (default 0.15)
- speed: change animation duration (default 0.5s)
- texture: swap the background URL for a different pattern
- z-index: adjust #grain-overlay z-index to layer correctly
-->
<div
id="grain-overlay"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 100;
"
>
<div class="grain-texture"></div>
</div>
<style>
@keyframes hf-grain-noise {
0%,
100% {
transform: translate(0, 0);
}
10% {
transform: translate(-5%, -5%);
}
20% {
transform: translate(-10%, 5%);
}
30% {
transform: translate(5%, -10%);
}
40% {
transform: translate(-5%, 15%);
}
50% {
transform: translate(-10%, 5%);
}
60% {
transform: translate(15%, 0);
}
70% {
transform: translate(0, 10%);
}
80% {
transform: translate(-15%, 0);
}
90% {
transform: translate(10%, 5%);
}
}
#grain-overlay .grain-texture {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
opacity: 0.15;
animation: hf-grain-noise 0.5s steps(1) infinite;
}
</style>
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "grain-overlay",
"type": "hyperframes:component",
"title": "Grain Overlay",
"description": "Animated film grain texture overlay using CSS keyframes — adds warmth and analog character to any composition",
"tags": ["texture", "grain", "overlay", "film"],
"files": [
{
"path": "grain-overlay.html",
"target": "compositions/components/grain-overlay.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/grain-overlay.png"
}
}
+175
View File
@@ -0,0 +1,175 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Grid Pixelate Wipe — Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="grid-pixelate-wipe-demo"
data-width="1920"
data-height="1080"
data-duration="8"
>
<!-- Scene A -->
<div id="scene-a" class="demo-scene scene-a">
<div style="text-align: center; color: white">
<div style="font-size: 80px; font-weight: 800">Scene A</div>
<div style="font-size: 32px; margin-top: 20px; opacity: 0.7">
The grid wipe dissolves this away
</div>
</div>
</div>
<!-- Scene B (hidden initially) -->
<div id="scene-b" class="demo-scene scene-b">
<div style="text-align: center; color: white">
<div style="font-size: 80px; font-weight: 800">Scene B</div>
<div style="font-size: 32px; margin-top: 20px; opacity: 0.7">Revealed from the grid</div>
</div>
</div>
<!-- Grid Pixelate Wipe overlay -->
<div
id="grid-pixelate-overlay"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999;
display: grid;
"
></div>
<style>
#grid-pixelate-overlay {
--grid-color: black;
}
#grid-pixelate-overlay .grid-cell {
background: var(--grid-color);
transform: scale(0);
transform-origin: center center;
}
</style>
<style>
.demo-scene {
position: absolute;
width: 1920px;
height: 1080px;
display: flex;
justify-content: center;
align-items: center;
font-family: "Inter", sans-serif;
}
.scene-a {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.scene-b {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
opacity: 0;
}
</style>
<script>
(function () {
const COLS = 16;
const ROWS = 9;
const overlay = document.getElementById("grid-pixelate-overlay");
overlay.style.gridTemplateColumns = `repeat(${COLS}, 1fr)`;
overlay.style.gridTemplateRows = `repeat(${ROWS}, 1fr)`;
for (let i = 0; i < COLS * ROWS; i++) {
const cell = document.createElement("div");
cell.className = "grid-cell";
overlay.appendChild(cell);
}
const tl = gsap.timeline({ paused: true });
// Scene A visible for 2 seconds
// Grid covers screen (transition out of A)
tl.to(
".grid-cell",
{
scale: 1,
duration: 0.6,
stagger: { amount: 0.6, from: "center" },
ease: "power2.inOut",
},
2.0,
);
// Swap scenes at midpoint
tl.set("#scene-a", { opacity: 0 }, 3.0);
tl.set("#scene-b", { opacity: 1 }, 3.0);
// Grid reveals scene B
tl.to(
".grid-cell",
{
scale: 0,
duration: 0.6,
stagger: { amount: 0.6, from: "edges" },
ease: "power2.inOut",
},
3.0,
);
// Second transition: back to A using random pattern
tl.to(
".grid-cell",
{
scale: 1,
duration: 0.5,
stagger: { amount: 0.5, from: "random" },
ease: "power2.inOut",
},
5.5,
);
tl.set("#scene-b", { opacity: 0 }, 6.3);
tl.set("#scene-a", { opacity: 1 }, 6.3);
tl.to(
".grid-cell",
{
scale: 0,
duration: 0.5,
stagger: { amount: 0.5, from: "random" },
ease: "power2.inOut",
},
6.3,
);
window.__timelines = window.__timelines || {};
window.__timelines["grid-pixelate-wipe-demo"] = tl;
})();
</script>
</div>
</body>
</html>
@@ -0,0 +1,80 @@
<!--
Grid Pixelate Wipe — transition effect.
Usage: place this overlay in your composition. To transition between
scenes, animate .grid-cell scale from 0→1 (cover) then 1→0 (reveal)
using GSAP stagger with a "from" pattern.
Customize:
- COLS / ROWS: grid density (default 16×9), edit in the script below
- --grid-color: cell fill color (default black)
- Stagger "from": "center" for radial, "edges" for inward, "random" for scatter
-->
<div
id="grid-pixelate-overlay"
style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999;
display: grid;
"
></div>
<style>
#grid-pixelate-overlay {
--grid-color: black;
}
#grid-pixelate-overlay .grid-cell {
background: var(--grid-color);
transform: scale(0);
transform-origin: center center;
}
</style>
<script>
(function () {
const COLS = 16;
const ROWS = 9;
const overlay = document.getElementById("grid-pixelate-overlay");
if (!overlay) return;
overlay.style.gridTemplateColumns = `repeat(${COLS}, 1fr)`;
overlay.style.gridTemplateRows = `repeat(${ROWS}, 1fr)`;
for (let i = 0; i < COLS * ROWS; i++) {
const cell = document.createElement("div");
cell.className = "grid-cell";
overlay.appendChild(cell);
}
})();
</script>
<!--
Timeline integration example:
// Cover screen with grid (transition out of scene A)
tl.to("#grid-pixelate-overlay .grid-cell", {
scale: 1,
duration: 0.6,
stagger: { amount: 0.6, from: "center" },
ease: "power2.inOut",
}, 3.0);
// Swap scene content at midpoint
tl.set("#scene-a", { opacity: 0 }, 3.6);
tl.set("#scene-b", { opacity: 1 }, 3.6);
// Reveal scene B by removing grid
tl.to("#grid-pixelate-overlay .grid-cell", {
scale: 0,
duration: 0.6,
stagger: { amount: 0.6, from: "edges" },
ease: "power2.inOut",
}, 3.6);
-->
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "grid-pixelate-wipe",
"type": "hyperframes:component",
"title": "Grid Pixelate Wipe",
"description": "Transition effect where the screen dissolves into a grid of squares that fade out with staggered timing — use between scenes",
"tags": ["transition", "wipe", "grid", "pixelate"],
"files": [
{
"path": "grid-pixelate-wipe.html",
"target": "compositions/components/grid-pixelate-wipe.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/grid-pixelate-wipe.png"
}
}
+237
View File
@@ -0,0 +1,237 @@
<!-- demo.html — used by scripts/generate-catalog-previews.ts to render the
catalog preview video. Not installed by `hyperframes add morph-text`.
Keep in sync with morph-text.html. -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Morph Text</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=Figtree:wght@900&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #fff;
}
#morph-text {
position: relative;
width: 1920px;
height: 1080px;
}
/* Word list: hidden source of truth — edit <li> items in Studio. */
#morph-words {
display: none;
list-style: none;
margin: 0;
padding: 0;
}
#morph-filter {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}
#morph-wrapper {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 1920px;
height: 210px;
filter: drop-shadow(0 10px 44px rgba(0, 0, 0, 0.5))
drop-shadow(0 3px 10px rgba(0, 0, 0, 0.25));
}
#morph-display {
width: 100%;
height: 100%;
filter: none;
}
#morph-a,
#morph-b {
position: absolute;
width: 100%;
display: inline-block;
text-align: center;
font-size: 120pt;
font-weight: 900;
line-height: 1;
user-select: none;
}
</style>
</head>
<body>
<div
id="morph-text"
data-composition-id="morph-text"
data-timeline-locked
data-width="1920"
data-height="1080"
data-duration="15"
data-fps="30"
data-morph-speed="1"
data-morph-pause="1.5"
>
<!--
Edit words here — one <li> per word or phrase.
data-font : CSS font-family (e.g. "'Figtree', sans-serif")
data-color : text color hex (e.g. "#000000")
Adjust data-morph-speed (seconds per morph) and data-morph-pause (hold time) above.
-->
<ol id="morph-words" aria-hidden="true">
<li data-font="'Figtree', sans-serif" data-color="#000000">Do more with less.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Built for what's next.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Fast. Focused. Powerful.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Your work, amplified.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Start free today.</li>
</ol>
<svg id="morph-filter" aria-hidden="true" focusable="false">
<defs>
<filter id="morph-threshold">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 255 -100"
/>
</filter>
</defs>
</svg>
<div id="morph-wrapper">
<div id="morph-display">
<span id="morph-a"></span>
<span id="morph-b"></span>
</div>
</div>
</div>
<script>
(function () {
"use strict";
var container = document.getElementById("morph-text");
var display = document.getElementById("morph-display");
var elA = document.getElementById("morph-a");
var elB = document.getElementById("morph-b");
var words = Array.from(document.querySelectorAll("#morph-words li"))
.map(function (el) {
return {
text: el.textContent.trim(),
font: el.dataset.font || "'Figtree', sans-serif",
color: el.dataset.color || "#111111",
};
})
.filter(function (w) {
return w.text;
});
if (words.length < 2) {
words = [
{ text: "Morph", font: "'Figtree', sans-serif", color: "#111111" },
{ text: "Text", font: "'Figtree', sans-serif", color: "#111111" },
];
}
var morphSpeed = parseFloat(container.dataset.morphSpeed || "1");
var morphPause = parseFloat(container.dataset.morphPause || "1.5");
var duration = parseFloat(container.dataset.duration || "9");
var stepDur = morphSpeed + morphPause;
var totalWordDur = words.length * stepDur;
function applyMorph(T) {
var t = T % totalWordDur;
var stepIdx = Math.floor(t / stepDur);
var stepT = t - stepIdx * stepDur;
var frac = Math.min(stepT / morphSpeed, 1);
var wA = words[stepIdx % words.length];
var wB = words[(stepIdx + 1) % words.length];
elA.textContent = wA.text;
elA.style.fontFamily = wA.font;
elA.style.color = wA.color;
elB.textContent = wB.text;
elB.style.fontFamily = wB.font;
elB.style.color = wB.color;
if (frac <= 0) {
display.style.filter = "none";
elA.style.filter = "";
elA.style.opacity = "1";
elB.style.filter = "";
elB.style.opacity = "0";
} else if (frac >= 1) {
display.style.filter = "none";
elA.style.filter = "";
elA.style.opacity = "0";
elB.style.filter = "";
elB.style.opacity = "1";
} else {
display.style.filter = "url(#morph-threshold)";
var blurB = Math.max(0, Math.min(8 / frac - 8, 100));
var blurA = Math.max(0, Math.min(8 / (1 - frac) - 8, 100));
elB.style.filter = "blur(" + blurB + "px)";
elB.style.opacity = frac;
elA.style.filter = "blur(" + blurA + "px)";
elA.style.opacity = 1 - frac;
}
}
if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
elA.textContent = words[0].text;
elA.style.fontFamily = words[0].font;
elA.style.color = words[0].color;
elA.style.opacity = "1";
display.style.filter = "none";
return;
}
var proxy = { t: 0 };
var tl = gsap.timeline({ paused: true });
tl.to(proxy, {
t: duration,
duration: duration,
ease: "none",
onUpdate: function () {
applyMorph(proxy.t);
},
});
applyMorph(0);
tl.seek(0);
window.__timelines = window.__timelines || {};
window.__timelines["morph-text"] = tl;
})();
</script>
</body>
</html>
+234
View File
@@ -0,0 +1,234 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Morph Text</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=Figtree:wght@900&display=block"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #fff;
}
#morph-text {
position: relative;
width: 1920px;
height: 1080px;
}
/* Word list: hidden source of truth — edit <li> items in Studio. */
#morph-words {
display: none;
list-style: none;
margin: 0;
padding: 0;
}
#morph-filter {
position: absolute;
width: 0;
height: 0;
overflow: hidden;
pointer-events: none;
}
#morph-wrapper {
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 1920px;
height: 210px;
filter: drop-shadow(0 10px 44px rgba(0, 0, 0, 0.5))
drop-shadow(0 3px 10px rgba(0, 0, 0, 0.25));
}
#morph-display {
width: 100%;
height: 100%;
filter: none;
}
#morph-a,
#morph-b {
position: absolute;
width: 100%;
display: inline-block;
text-align: center;
font-size: 120pt;
font-weight: 900;
line-height: 1;
user-select: none;
}
</style>
</head>
<body>
<div
id="morph-text"
data-composition-id="morph-text"
data-timeline-locked
data-width="1920"
data-height="1080"
data-duration="15"
data-fps="30"
data-morph-speed="1"
data-morph-pause="1.5"
>
<!--
Edit words here — one <li> per word or phrase.
data-font : CSS font-family (e.g. "'Figtree', sans-serif")
data-color : text color hex (e.g. "#000000")
Adjust data-morph-speed (seconds per morph) and data-morph-pause (hold time) above.
-->
<ol id="morph-words" aria-hidden="true">
<li data-font="'Figtree', sans-serif" data-color="#000000">Do more with less.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Built for what's next.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Fast. Focused. Powerful.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Your work, amplified.</li>
<li data-font="'Figtree', sans-serif" data-color="#000000">Start free today.</li>
</ol>
<svg id="morph-filter" aria-hidden="true" focusable="false">
<defs>
<filter id="morph-threshold">
<feColorMatrix
in="SourceGraphic"
type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 255 -100"
/>
</filter>
</defs>
</svg>
<div id="morph-wrapper">
<div id="morph-display">
<span id="morph-a"></span>
<span id="morph-b"></span>
</div>
</div>
</div>
<script>
(function () {
"use strict";
var container = document.getElementById("morph-text");
var display = document.getElementById("morph-display");
var elA = document.getElementById("morph-a");
var elB = document.getElementById("morph-b");
var words = Array.from(document.querySelectorAll("#morph-words li"))
.map(function (el) {
return {
text: el.textContent.trim(),
font: el.dataset.font || "'Figtree', sans-serif",
color: el.dataset.color || "#111111",
};
})
.filter(function (w) {
return w.text;
});
if (words.length < 2) {
words = [
{ text: "Morph", font: "'Figtree', sans-serif", color: "#111111" },
{ text: "Text", font: "'Figtree', sans-serif", color: "#111111" },
];
}
var morphSpeed = parseFloat(container.dataset.morphSpeed || "1");
var morphPause = parseFloat(container.dataset.morphPause || "1.5");
var duration = parseFloat(container.dataset.duration || "9");
var stepDur = morphSpeed + morphPause;
var totalWordDur = words.length * stepDur;
function applyMorph(T) {
var t = T % totalWordDur;
var stepIdx = Math.floor(t / stepDur);
var stepT = t - stepIdx * stepDur;
var frac = Math.min(stepT / morphSpeed, 1);
var wA = words[stepIdx % words.length];
var wB = words[(stepIdx + 1) % words.length];
elA.textContent = wA.text;
elA.style.fontFamily = wA.font;
elA.style.color = wA.color;
elB.textContent = wB.text;
elB.style.fontFamily = wB.font;
elB.style.color = wB.color;
if (frac <= 0) {
display.style.filter = "none";
elA.style.filter = "";
elA.style.opacity = "1";
elB.style.filter = "";
elB.style.opacity = "0";
} else if (frac >= 1) {
display.style.filter = "none";
elA.style.filter = "";
elA.style.opacity = "0";
elB.style.filter = "";
elB.style.opacity = "1";
} else {
display.style.filter = "url(#morph-threshold)";
var blurB = Math.max(0, Math.min(8 / frac - 8, 100));
var blurA = Math.max(0, Math.min(8 / (1 - frac) - 8, 100));
elB.style.filter = "blur(" + blurB + "px)";
elB.style.opacity = frac;
elA.style.filter = "blur(" + blurA + "px)";
elA.style.opacity = 1 - frac;
}
}
if (matchMedia("(prefers-reduced-motion: reduce)").matches) {
elA.textContent = words[0].text;
elA.style.fontFamily = words[0].font;
elA.style.color = words[0].color;
elA.style.opacity = "1";
display.style.filter = "none";
return;
}
var proxy = { t: 0 };
var tl = gsap.timeline({ paused: true });
tl.to(proxy, {
t: duration,
duration: duration,
ease: "none",
onUpdate: function () {
applyMorph(proxy.t);
},
});
applyMorph(0);
tl.seek(0);
window.__timelines = window.__timelines || {};
window.__timelines["morph-text"] = tl;
})();
</script>
</body>
</html>
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "morph-text",
"type": "hyperframes:component",
"title": "Morph Text",
"description": "Gooey text morph — cycles through an editable word list using SVG threshold + GSAP-driven blur for a fluid, satisfying transition effect",
"tags": ["text", "text-effect", "typography", "morph", "gooey", "kinetic", "animation"],
"files": [
{
"path": "morph-text.html",
"target": "compositions/components/morph-text.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"video": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/morph-text.mp4"
}
}
+543
View File
@@ -0,0 +1,543 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Motion Blur — Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #06060a;
}
#demo {
position: relative;
width: 1920px;
height: 1080px;
overflow: hidden;
font-family: "SF Mono", ui-monospace, monospace;
background: #06060a;
}
.header {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 80px;
display: flex;
align-items: flex-end;
padding: 0 72px 20px;
border-bottom: 1px solid #0f0f18;
}
.header-title {
font-size: 11px;
letter-spacing: 0.4em;
text-transform: uppercase;
color: #1e1e2a;
}
.header-sub {
margin-left: auto;
font-size: 11px;
letter-spacing: 0.25em;
text-transform: uppercase;
color: #14141c;
}
.group {
position: absolute;
left: 0;
right: 0;
height: 184px;
}
.group::after {
content: "";
position: absolute;
bottom: 0;
left: 72px;
right: 72px;
height: 1px;
background: #0d0d15;
}
.dur {
position: absolute;
left: 72px;
top: 0;
width: 90px;
height: 160px;
display: flex;
flex-direction: column;
justify-content: center;
}
.dur-num {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.04em;
color: #fff;
line-height: 1;
}
.dur-unit {
font-size: 9px;
letter-spacing: 0.25em;
text-transform: uppercase;
color: #252532;
margin-top: 5px;
}
.row-hint {
position: absolute;
left: 176px;
width: 44px;
height: 72px;
display: flex;
align-items: center;
font-size: 8px;
letter-spacing: 0.2em;
text-transform: uppercase;
color: #161620;
}
.spd {
position: absolute;
right: 72px;
top: 0;
width: 100px;
height: 160px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
}
.spd-label {
font-size: 10px;
letter-spacing: 0.3em;
text-transform: uppercase;
color: #181822;
}
.pill {
position: absolute;
left: 0;
top: 0;
width: 200px;
height: 72px;
border-radius: 36px;
will-change: transform, filter;
}
.word {
position: absolute;
left: 0;
top: 88px;
height: 72px;
line-height: 72px;
white-space: nowrap;
font-size: 56px;
font-weight: 800;
letter-spacing: -0.03em;
will-change: transform, filter;
}
.inner-div {
position: absolute;
left: 220px;
right: 200px;
top: 80px;
height: 1px;
background: #0a0a12;
}
.track-a,
.track-b {
position: absolute;
left: 220px;
right: 200px;
height: 1px;
background: #0c0c14;
}
.track-a {
top: 35px;
}
.track-b {
top: 123px;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="motion-blur-demo"
data-width="1920"
data-height="1080"
data-start="0"
data-duration="4"
>
<div class="header">
<span class="header-title">Motion Blur — Velocity Showcase</span>
<span class="header-sub">feGaussianBlur + scaleX · blur ∝ velocity</span>
</div>
<div class="group" style="top: 80px">
<div class="dur"><span class="dur-num">3.0</span><span class="dur-unit">seconds</span></div>
<div class="row-hint" style="top: 0">shape</div>
<div class="row-hint" style="top: 88px">text</div>
<div class="track-a"></div>
<div class="inner-div"></div>
<div class="track-b"></div>
<div
class="pill"
id="s1"
style="
background: linear-gradient(135deg, #60a5fa, #2563eb);
box-shadow: 0 0 32px rgba(96, 165, 250, 0.18);
"
></div>
<div
class="word"
id="t1"
style="color: #3b82f6; text-shadow: 0 0 40px rgba(96, 165, 250, 0.3)"
>
DRIFT
</div>
<div class="spd"><span class="spd-label">slow</span></div>
</div>
<div class="group" style="top: 264px">
<div class="dur"><span class="dur-num">1.5</span><span class="dur-unit">seconds</span></div>
<div class="row-hint" style="top: 0">shape</div>
<div class="row-hint" style="top: 88px">text</div>
<div class="track-a"></div>
<div class="inner-div"></div>
<div class="track-b"></div>
<div
class="pill"
id="s2"
style="
background: linear-gradient(135deg, #34d399, #059669);
box-shadow: 0 0 32px rgba(52, 211, 153, 0.18);
"
></div>
<div
class="word"
id="t2"
style="color: #10b981; text-shadow: 0 0 40px rgba(52, 211, 153, 0.3)"
>
GLIDE
</div>
<div class="spd"><span class="spd-label">medium</span></div>
</div>
<div class="group" style="top: 448px">
<div class="dur"><span class="dur-num">0.7</span><span class="dur-unit">seconds</span></div>
<div class="row-hint" style="top: 0">shape</div>
<div class="row-hint" style="top: 88px">text</div>
<div class="track-a"></div>
<div class="inner-div"></div>
<div class="track-b"></div>
<div
class="pill"
id="s3"
style="
background: linear-gradient(135deg, #c084fc, #7c3aed);
box-shadow: 0 0 32px rgba(192, 132, 252, 0.18);
"
></div>
<div
class="word"
id="t3"
style="color: #a855f7; text-shadow: 0 0 40px rgba(192, 132, 252, 0.3)"
>
RUSH
</div>
<div class="spd"><span class="spd-label">fast</span></div>
</div>
<div class="group" style="top: 632px">
<div class="dur">
<span class="dur-num">0.35</span><span class="dur-unit">seconds</span>
</div>
<div class="row-hint" style="top: 0">shape</div>
<div class="row-hint" style="top: 88px">text</div>
<div class="track-a"></div>
<div class="inner-div"></div>
<div class="track-b"></div>
<div
class="pill"
id="s4"
style="
background: linear-gradient(135deg, #fb923c, #ea580c);
box-shadow: 0 0 32px rgba(251, 146, 60, 0.18);
"
></div>
<div
class="word"
id="t4"
style="color: #f97316; text-shadow: 0 0 40px rgba(251, 146, 60, 0.3)"
>
BLAST
</div>
<div class="spd"><span class="spd-label">faster</span></div>
</div>
<div class="group" style="top: 816px">
<div class="dur"><span class="dur-num">0.2</span><span class="dur-unit">seconds</span></div>
<div class="row-hint" style="top: 0">shape</div>
<div class="row-hint" style="top: 88px">text</div>
<div class="track-a"></div>
<div class="inner-div"></div>
<div class="track-b"></div>
<div
class="pill"
id="s5"
style="
background: linear-gradient(135deg, #f87171, #dc2626);
box-shadow: 0 0 32px rgba(248, 113, 113, 0.18);
"
></div>
<div
class="word"
id="t5"
style="color: #ef4444; text-shadow: 0 0 40px rgba(248, 113, 113, 0.3)"
>
WARP
</div>
<div class="spd"><span class="spd-label">extreme</span></div>
</div>
<script>
(function () {
if (!window._hfMbUid) window._hfMbUid = 0;
var ns = "http://www.w3.org/2000/svg";
window.attachMotionBlur = function (selector, tl, opts) {
opts = opts || {};
var blurScale = opts.blurScale !== undefined ? opts.blurScale : 0.008;
var blurMax = opts.blurMax !== undefined ? opts.blurMax : 20;
var stretchScale = opts.stretchScale !== undefined ? opts.stretchScale : 0.0002;
var stretchMax = opts.stretchMax !== undefined ? opts.stretchMax : 0;
var axis = opts.axis || "both";
var items = Array.isArray(selector) ? selector : [selector];
var targets = items.reduce(function (acc, s) {
if (typeof s === "string") {
document.querySelectorAll(s).forEach(function (el) {
acc.push(el);
});
} else if (s instanceof Element) {
acc.push(s);
}
return acc;
}, []);
var GHOST_CFG = [
{ frac: 0.25, slope: 0.55 },
{ frac: 0.55, slope: 0.28 },
{ frac: 1.0, slope: 0.1 },
];
var state = targets.map(function (el) {
var uid = "hf-mb-" + window._hfMbUid++;
var svg = document.createElementNS(ns, "svg");
svg.setAttribute("style", "position:absolute;width:0;height:0;overflow:hidden;");
var filter = document.createElementNS(ns, "filter");
filter.id = uid;
filter.setAttribute("x", "-110%");
filter.setAttribute("y", "-25%");
filter.setAttribute("width", "260%");
filter.setAttribute("height", "150%");
var ghosts = GHOST_CFG.map(function (cfg, gi) {
var feOff = document.createElementNS(ns, "feOffset");
feOff.setAttribute("in", "SourceGraphic");
feOff.setAttribute("dx", "0");
feOff.setAttribute("dy", "0");
feOff.setAttribute("result", "go" + gi);
var feGB = document.createElementNS(ns, "feGaussianBlur");
feGB.setAttribute("in", "go" + gi);
feGB.setAttribute("stdDeviation", "0 0");
feGB.setAttribute("result", "gb" + gi);
var feCT = document.createElementNS(ns, "feComponentTransfer");
feCT.setAttribute("in", "gb" + gi);
feCT.setAttribute("result", "gf" + gi);
var feFuncA = document.createElementNS(ns, "feFuncA");
feFuncA.setAttribute("type", "linear");
feFuncA.setAttribute("slope", String(cfg.slope));
feCT.appendChild(feFuncA);
filter.appendChild(feOff);
filter.appendChild(feGB);
filter.appendChild(feCT);
return { feOff: feOff, feGB: feGB, frac: cfg.frac };
});
var feTopBlur = document.createElementNS(ns, "feGaussianBlur");
feTopBlur.setAttribute("in", "SourceGraphic");
feTopBlur.setAttribute("stdDeviation", "0 0");
feTopBlur.setAttribute("result", "top");
filter.appendChild(feTopBlur);
var feMerge = document.createElementNS(ns, "feMerge");
for (var mi = GHOST_CFG.length - 1; mi >= 0; mi--) {
var mn = document.createElementNS(ns, "feMergeNode");
mn.setAttribute("in", "gf" + mi);
feMerge.appendChild(mn);
}
var mnTop = document.createElementNS(ns, "feMergeNode");
mnTop.setAttribute("in", "top");
feMerge.appendChild(mnTop);
filter.appendChild(feMerge);
svg.appendChild(filter);
document.body.appendChild(svg);
return {
el: el,
ghosts: ghosts,
feTopBlur: feTopBlur,
filterId: uid,
prevX: parseFloat(gsap.getProperty(el, "x")) || 0,
prevY: parseFloat(gsap.getProperty(el, "y")) || 0,
prevTime: tl.time(),
};
});
var _proxy = { t: 0 };
tl.to(
_proxy,
{
t: 1,
duration: Math.max(tl.duration(), 0.1),
ease: "none",
onUpdate: function () {
var time = tl.time();
state.forEach(function (s) {
var x = parseFloat(gsap.getProperty(s.el, "x")) || 0;
var y = parseFloat(gsap.getProperty(s.el, "y")) || 0;
var dt = time - s.prevTime;
if (dt > 0.0005) {
var vx = axis !== "y" ? (x - s.prevX) / dt : 0;
var vy = axis !== "x" ? (y - s.prevY) / dt : 0;
var bx = Math.min(Math.abs(vx) * blurScale, blurMax);
var by = Math.min(Math.abs(vy) * blurScale, blurMax);
var bxFinal = axis !== "y" ? bx : Math.max(by * 0.08, 0.4);
var byFinal = axis !== "x" ? by : Math.max(bx * 0.08, 0.4);
var active = bx > 0.3 || by > 0.3;
if (active) {
s.el.style.filter = "url(#" + s.filterId + ")";
s.ghosts.forEach(function (g) {
var dx =
axis !== "y" ? (vx >= 0 ? -bxFinal * g.frac : bxFinal * g.frac) : 0;
var dy =
axis !== "x" ? (vy >= 0 ? -byFinal * g.frac : byFinal * g.frac) : 0;
g.feOff.setAttribute("dx", dx.toFixed(2));
g.feOff.setAttribute("dy", dy.toFixed(2));
var gbx =
axis !== "y"
? (bxFinal * g.frac * 0.5).toFixed(2)
: Math.max(byFinal * g.frac * 0.04, 0.4).toFixed(2);
var gby =
axis !== "x"
? (byFinal * g.frac * 0.5).toFixed(2)
: Math.max(bxFinal * g.frac * 0.04, 0.4).toFixed(2);
g.feGB.setAttribute("stdDeviation", gbx + " " + gby);
});
s.feTopBlur.setAttribute(
"stdDeviation",
(bxFinal * 0.15).toFixed(2) + " " + (byFinal * 0.15).toFixed(2),
);
if (stretchMax > 0) {
var sx = 1 + Math.min(Math.abs(vx) * stretchScale, stretchMax);
var sy = 1 + Math.min(Math.abs(vy) * stretchScale, stretchMax);
var ox = axis !== "y" ? (vx >= 0 ? "100% 50%" : "0% 50%") : "50% 50%";
var oy = axis !== "x" ? (vy >= 0 ? "50% 100%" : "50% 0%") : "50% 50%";
gsap.set(s.el, {
scaleX: axis !== "y" ? sx : 1,
scaleY: axis !== "x" ? sy : 1,
transformOrigin: axis === "x" ? ox : axis === "y" ? oy : "50% 50%",
});
}
} else {
s.el.style.filter = "";
s.ghosts.forEach(function (g) {
g.feOff.setAttribute("dx", "0");
g.feOff.setAttribute("dy", "0");
g.feGB.setAttribute("stdDeviation", "0 0");
});
s.feTopBlur.setAttribute("stdDeviation", "0 0");
if (stretchMax > 0) {
gsap.set(s.el, { scaleX: 1, scaleY: 1, transformOrigin: "50% 50%" });
}
}
s.prevX = x;
s.prevY = y;
s.prevTime = time;
} else if (dt < -0.0005) {
s.el.style.filter = "";
s.ghosts.forEach(function (g) {
g.feOff.setAttribute("dx", "0");
g.feOff.setAttribute("dy", "0");
g.feGB.setAttribute("stdDeviation", "0 0");
});
s.feTopBlur.setAttribute("stdDeviation", "0 0");
if (stretchMax > 0) {
gsap.set(s.el, { scaleX: 1, scaleY: 1, transformOrigin: "50% 50%" });
}
s.prevX = x;
s.prevY = y;
s.prevTime = time;
}
// dt ≈ 0: double-fire — skip.
});
},
},
0,
);
};
var tl = gsap.timeline({ paused: true });
gsap.set(["#s1", "#s2", "#s3", "#s4", "#s5", "#t1", "#t2", "#t3", "#t4", "#t5"], {
x: -400,
});
tl.to(["#s1", "#t1"], { x: 1640, duration: 3.0, ease: "power3.inOut" }, 0);
tl.to(["#s2", "#t2"], { x: 1640, duration: 1.5, ease: "power3.inOut" }, 0);
tl.to(["#s3", "#t3"], { x: 1640, duration: 0.7, ease: "power3.inOut" }, 0);
tl.to(["#s4", "#t4"], { x: 1640, duration: 0.35, ease: "power3.inOut" }, 0);
tl.to(["#s5", "#t5"], { x: 1640, duration: 0.2, ease: "power3.inOut" }, 0);
tl.set(document.body, {}, 4);
attachMotionBlur(
["#s1", "#s2", "#s3", "#s4", "#s5", "#t1", "#t2", "#t3", "#t4", "#t5"],
tl,
{ axis: "x", blurMax: 80, stretchMax: 0 },
);
window.__timelines = window.__timelines || {};
window.__timelines["motion-blur-demo"] = tl;
})();
</script>
</div>
</body>
</html>
+255
View File
@@ -0,0 +1,255 @@
<!--
Motion Blur — velocity-driven directional motion blur.
Usage: paste this snippet into your composition, then call
attachMotionBlur() with any element animated by your GSAP timeline.
The snippet hooks into the timeline's onUpdate callback, tracks the
GSAP x/y position of each target frame-by-frame, and applies:
1. Ghost copies of the element at increasing backward offsets with
decreasing opacity — inherently one-sided, no forward blur component.
2. A small Gaussian blur at the current position so the element looks
in-motion (blurry) rather than sharp on top of the trail.
3. An optional scaleX/Y stretch in the direction of travel (off by
default; enable via stretchMax > 0 if you want the effect).
Both effects clear automatically when the element decelerates to rest.
Requirements:
- Elements must be animated via GSAP x/y (transform), not left/top.
- Call attachMotionBlur() AFTER defining all tweens, before
window.__timelines registration.
- GSAP must be loaded before this snippet executes.
API:
attachMotionBlur(selector, timeline, options?)
Options:
blurScale — directional blur per px/s of velocity (default 0.008)
blurMax — max blur radius on the motion axis in px (default 20)
stretchScale — scaleX/Y added per px/s (default 0.0002)
stretchMax — max stretch factor above 1.0 (default 0, disabled)
axis — "x" | "y" | "both" (default "both")
-->
<script>
(function () {
if (!window._hfMbUid) window._hfMbUid = 0;
window.attachMotionBlur = function (selector, tl, opts) {
opts = opts || {};
var blurScale = opts.blurScale !== undefined ? opts.blurScale : 0.008;
var blurMax = opts.blurMax !== undefined ? opts.blurMax : 20;
var stretchScale = opts.stretchScale !== undefined ? opts.stretchScale : 0.0002;
var stretchMax = opts.stretchMax !== undefined ? opts.stretchMax : 0;
var axis = opts.axis || "both";
var items = Array.isArray(selector) ? selector : [selector];
var targets = items.reduce(function (acc, s) {
if (typeof s === "string") {
document.querySelectorAll(s).forEach(function (el) {
acc.push(el);
});
} else if (s instanceof Element) {
acc.push(s);
}
return acc;
}, []);
// One SVG filter per target. Ghost copies placed behind → inherently one-sided.
// Small top Gaussian makes element look blurry at current position, not sharp.
var ns = "http://www.w3.org/2000/svg";
var GHOST_CFG = [
{ frac: 0.25, slope: 0.55 },
{ frac: 0.55, slope: 0.28 },
{ frac: 1.0, slope: 0.1 },
];
var state = targets.map(function (el) {
var uid = "hf-mb-" + window._hfMbUid++;
var svg = document.createElementNS(ns, "svg");
svg.setAttribute("style", "position:absolute;width:0;height:0;overflow:hidden;");
var filter = document.createElementNS(ns, "filter");
filter.id = uid;
filter.setAttribute("x", "-110%");
filter.setAttribute("y", "-25%");
filter.setAttribute("width", "260%");
filter.setAttribute("height", "150%");
var ghosts = GHOST_CFG.map(function (cfg, gi) {
var feOff = document.createElementNS(ns, "feOffset");
feOff.setAttribute("in", "SourceGraphic");
feOff.setAttribute("dx", "0");
feOff.setAttribute("dy", "0");
feOff.setAttribute("result", "go" + gi);
var feGB = document.createElementNS(ns, "feGaussianBlur");
feGB.setAttribute("in", "go" + gi);
feGB.setAttribute("stdDeviation", "0 0");
feGB.setAttribute("result", "gb" + gi);
var feCT = document.createElementNS(ns, "feComponentTransfer");
feCT.setAttribute("in", "gb" + gi);
feCT.setAttribute("result", "gf" + gi);
var feFuncA = document.createElementNS(ns, "feFuncA");
feFuncA.setAttribute("type", "linear");
feFuncA.setAttribute("slope", String(cfg.slope));
feCT.appendChild(feFuncA);
filter.appendChild(feOff);
filter.appendChild(feGB);
filter.appendChild(feCT);
return { feOff: feOff, feGB: feGB, frac: cfg.frac };
});
var feTopBlur = document.createElementNS(ns, "feGaussianBlur");
feTopBlur.setAttribute("in", "SourceGraphic");
feTopBlur.setAttribute("stdDeviation", "0 0");
feTopBlur.setAttribute("result", "top");
filter.appendChild(feTopBlur);
var feMerge = document.createElementNS(ns, "feMerge");
for (var mi = GHOST_CFG.length - 1; mi >= 0; mi--) {
var mn = document.createElementNS(ns, "feMergeNode");
mn.setAttribute("in", "gf" + mi);
feMerge.appendChild(mn);
}
var mnTop = document.createElementNS(ns, "feMergeNode");
mnTop.setAttribute("in", "top");
feMerge.appendChild(mnTop);
filter.appendChild(feMerge);
svg.appendChild(filter);
document.body.appendChild(svg);
return {
el: el,
ghosts: ghosts,
feTopBlur: feTopBlur,
filterId: uid,
prevX: parseFloat(gsap.getProperty(el, "x")) || 0,
prevY: parseFloat(gsap.getProperty(el, "y")) || 0,
prevTime: tl.time(),
};
});
// tl.eventCallback("onUpdate") is not available in the HyperFrames renderer —
// the runtime proxies the timeline object. Tween onUpdate fires on every seek.
var _proxy = { t: 0 };
tl.to(
_proxy,
{
t: 1,
duration: Math.max(tl.duration(), 0.1),
ease: "none",
onUpdate: function () {
var time = tl.time();
state.forEach(function (s) {
var x = parseFloat(gsap.getProperty(s.el, "x")) || 0;
var y = parseFloat(gsap.getProperty(s.el, "y")) || 0;
var dt = time - s.prevTime;
if (dt > 0.0005) {
var vx = axis !== "y" ? (x - s.prevX) / dt : 0;
var vy = axis !== "x" ? (y - s.prevY) / dt : 0;
var bx = Math.min(Math.abs(vx) * blurScale, blurMax);
var by = Math.min(Math.abs(vy) * blurScale, blurMax);
var bxFinal = axis !== "y" ? bx : Math.max(by * 0.08, 0.4);
var byFinal = axis !== "x" ? by : Math.max(bx * 0.08, 0.4);
var active = bx > 0.3 || by > 0.3;
if (active) {
s.el.style.filter = "url(#" + s.filterId + ")";
s.ghosts.forEach(function (g) {
var dx = axis !== "y" ? (vx >= 0 ? -bxFinal * g.frac : bxFinal * g.frac) : 0;
var dy = axis !== "x" ? (vy >= 0 ? -byFinal * g.frac : byFinal * g.frac) : 0;
g.feOff.setAttribute("dx", dx.toFixed(2));
g.feOff.setAttribute("dy", dy.toFixed(2));
var gbx =
axis !== "y"
? (bxFinal * g.frac * 0.5).toFixed(2)
: Math.max(byFinal * g.frac * 0.04, 0.4).toFixed(2);
var gby =
axis !== "x"
? (byFinal * g.frac * 0.5).toFixed(2)
: Math.max(bxFinal * g.frac * 0.04, 0.4).toFixed(2);
g.feGB.setAttribute("stdDeviation", gbx + " " + gby);
});
s.feTopBlur.setAttribute(
"stdDeviation",
(bxFinal * 0.15).toFixed(2) + " " + (byFinal * 0.15).toFixed(2),
);
if (stretchMax > 0) {
var sx = 1 + Math.min(Math.abs(vx) * stretchScale, stretchMax);
var sy = 1 + Math.min(Math.abs(vy) * stretchScale, stretchMax);
var ox = axis !== "y" ? (vx >= 0 ? "100% 50%" : "0% 50%") : "50% 50%";
var oy = axis !== "x" ? (vy >= 0 ? "50% 100%" : "50% 0%") : "50% 50%";
gsap.set(s.el, {
scaleX: axis !== "y" ? sx : 1,
scaleY: axis !== "x" ? sy : 1,
transformOrigin: axis === "x" ? ox : axis === "y" ? oy : "50% 50%",
});
}
} else {
s.el.style.filter = "";
s.ghosts.forEach(function (g) {
g.feOff.setAttribute("dx", "0");
g.feOff.setAttribute("dy", "0");
g.feGB.setAttribute("stdDeviation", "0 0");
});
s.feTopBlur.setAttribute("stdDeviation", "0 0");
if (stretchMax > 0) {
gsap.set(s.el, { scaleX: 1, scaleY: 1, transformOrigin: "50% 50%" });
}
}
s.prevX = x;
s.prevY = y;
s.prevTime = time;
} else if (dt < -0.0005) {
s.el.style.filter = "";
s.ghosts.forEach(function (g) {
g.feOff.setAttribute("dx", "0");
g.feOff.setAttribute("dy", "0");
g.feGB.setAttribute("stdDeviation", "0 0");
});
s.feTopBlur.setAttribute("stdDeviation", "0 0");
if (stretchMax > 0) {
gsap.set(s.el, { scaleX: 1, scaleY: 1, transformOrigin: "50% 50%" });
}
s.prevX = x;
s.prevY = y;
s.prevTime = time;
}
// dt ≈ 0: double-fire — skip.
});
},
},
0,
);
};
})();
</script>
<!--
Timeline integration example:
const tl = gsap.timeline({ paused: true });
tl.fromTo("#my-box", { x: -100 }, { x: 1700, duration: 1.2, ease: "power3.inOut" }, 0.5);
// Extend to data-duration so seeks past the last tween reach the blur callback.
tl.set(document.body, {}, DATA_DURATION);
// Call AFTER tweens, BEFORE window.__timelines registration.
// attachMotionBlur adds a tracking tween with onUpdate — must be called after
// tl.set()/tl.to() have established the final timeline duration.
attachMotionBlur("#my-box", tl, { axis: "x" });
window.__timelines = window.__timelines || {};
window.__timelines["my-composition"] = tl;
-->
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "motion-blur",
"type": "hyperframes:component",
"title": "Motion Blur",
"description": "Velocity-driven motion blur — samples element position each frame and applies a one-sided SVG feGaussianBlur ghost trail proportional to speed",
"tags": ["effect", "motion-blur", "velocity", "animation", "physics"],
"files": [
{
"path": "motion-blur.html",
"target": "compositions/components/motion-blur.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/motion-blur.png"
}
}
+325
View File
@@ -0,0 +1,325 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Parallax Unzoom — Demo</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@400;600;800;900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
overflow: hidden;
background: #1a1a20;
font-family: "Inter", system-ui, sans-serif;
}
.demo-canvas {
width: 1920px;
height: 1080px;
position: relative;
overflow: hidden;
}
.parallax-unzoom-grid {
display: grid;
grid-template-columns: repeat(5, 360px);
grid-template-rows: repeat(3, 341px);
gap: 30px;
}
.parallax-unzoom-card {
border-radius: 22px;
overflow: hidden;
position: relative;
box-shadow: 0 12px 36px rgba(20, 20, 30, 0.08);
display: flex;
align-items: center;
justify-content: center;
}
.card-glyph {
font-size: 92px;
font-weight: 800;
color: rgba(255, 255, 255, 0.92);
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
letter-spacing: -0.04em;
line-height: 1;
}
.pu-c0 {
background: linear-gradient(135deg, #3a8b6f, #1d4d3d);
}
.pu-c1 {
background: linear-gradient(135deg, #8b4a3a, #4a2418);
}
.pu-c2 {
background: linear-gradient(135deg, #c89868, #8a6038);
}
.pu-c3 {
background: linear-gradient(135deg, #3a6088, #1a2f4a);
}
.pu-c4 {
background: linear-gradient(135deg, #b03838, #6a1818);
}
.pu-c5 {
background: linear-gradient(135deg, #bcbcbc, #6e6e6e);
}
.pu-c6 {
background: linear-gradient(135deg, #8a5fa0, #4a2860);
}
.pu-c7-focus {
background: linear-gradient(135deg, #f5c93f, #d99a18);
}
.pu-c8 {
background: linear-gradient(135deg, #d8d8d8, #9a9a9a);
}
.pu-c9 {
background: linear-gradient(135deg, #5fa37e, #2a6a4a);
}
.pu-c10 {
background: linear-gradient(135deg, #d1b66a, #8a7028);
}
.pu-c11 {
background: linear-gradient(135deg, #4f7fc0, #1f3f80);
}
.pu-c12 {
background: linear-gradient(135deg, #5a3a8f, #2a1858);
}
.pu-c13 {
background: linear-gradient(135deg, #b85a3a, #6a2818);
}
.pu-c14 {
background: linear-gradient(135deg, #f0f0e8, #c8c8be);
}
.parallax-unzoom-card[data-pu-focus="true"] {
grid-column: 3;
grid-row: 2;
}
.focus-label {
font-weight: 900;
font-size: 56px;
letter-spacing: -0.04em;
color: #1a1208;
text-align: center;
line-height: 1;
opacity: 0;
}
.focus-label small {
display: block;
font-size: 18px;
font-weight: 600;
letter-spacing: 0.18em;
margin-bottom: 14px;
color: rgba(26, 18, 8, 0.7);
}
.hero-headline {
position: absolute;
top: 80px;
left: 0;
width: 100%;
text-align: center;
font-size: 68px;
font-weight: 800;
letter-spacing: -0.03em;
color: #1a1a1a;
opacity: 1;
}
.parallax-unzoom-grid {
--pu-progress: 0;
--pu-focus-scale: 6;
--pu-sibling-push: 900px;
--pu-sibling-fade: 0;
position: relative;
}
.parallax-unzoom-card {
transform-origin: center center;
will-change: transform, opacity;
transform: translate3d(
calc(var(--pu-dx, 0px) * (1 - var(--pu-progress))),
calc(var(--pu-dy, 0px) * (1 - var(--pu-progress))),
0
)
scale(calc(0.7 + 0.3 * var(--pu-progress)));
opacity: calc(var(--pu-progress) * var(--pu-sibling-fade));
}
.parallax-unzoom-card[data-pu-focus="true"] {
z-index: 10;
transform: translate3d(0, 0, 0)
scale(calc(var(--pu-focus-scale) - (var(--pu-focus-scale) - 1) * var(--pu-progress)));
opacity: 1;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="parallax-unzoom-demo"
data-width="1920"
data-height="1080"
data-duration="6"
data-start="0"
>
<div class="demo-canvas">
<div class="hero-headline">Inspired by how people discover.</div>
<div class="parallax-unzoom-grid">
<div class="parallax-unzoom-card pu-c0" data-pu-row="0" data-pu-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c1" data-pu-row="0" data-pu-col="1">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c2" data-pu-row="0" data-pu-col="2">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c3" data-pu-row="0" data-pu-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c4" data-pu-row="0" data-pu-col="4">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c5" data-pu-row="1" data-pu-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c6" data-pu-row="1" data-pu-col="1">
<span class="card-glyph"></span>
</div>
<div
class="parallax-unzoom-card pu-c7-focus"
data-pu-row="1"
data-pu-col="2"
data-pu-focus="true"
>
<div class="focus-label">
<small>EBAY PLAYBOOK</small>
DISCOVER
</div>
</div>
<div class="parallax-unzoom-card pu-c8" data-pu-row="1" data-pu-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c9" data-pu-row="1" data-pu-col="4">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c10" data-pu-row="2" data-pu-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c11" data-pu-row="2" data-pu-col="1">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c12" data-pu-row="2" data-pu-col="2">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c13" data-pu-row="2" data-pu-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-unzoom-card pu-c14" data-pu-row="2" data-pu-col="4">
<span class="card-glyph" style="color: rgba(40, 40, 40, 0.7)"></span>
</div>
</div>
</div>
<script>
(function () {
const grids = document.querySelectorAll(".parallax-unzoom-grid");
grids.forEach((grid) => {
const cards = Array.from(grid.querySelectorAll(".parallax-unzoom-card"));
if (cards.length === 0) return;
let focal = cards.find((c) => c.dataset.puFocus === "true");
if (!focal) {
focal = cards[Math.floor(cards.length / 2)];
focal.dataset.puFocus = "true";
}
const focusRow = Number(focal.dataset.puRow ?? 0);
const focusCol = Number(focal.dataset.puCol ?? 0);
const push =
parseFloat(getComputedStyle(grid).getPropertyValue("--pu-sibling-push")) || 900;
cards.forEach((card) => {
if (card === focal) return;
const row = Number(card.dataset.puRow ?? 0);
const col = Number(card.dataset.puCol ?? 0);
const dCol = col - focusCol;
const dRow = row - focusRow;
const mag = Math.hypot(dCol, dRow) || 1;
const nx = (dCol / mag) * push * mag;
const ny = (dRow / mag) * push * mag;
card.style.setProperty("--pu-dx", `${nx}px`);
card.style.setProperty("--pu-dy", `${ny}px`);
});
});
const tl = gsap.timeline({ paused: true });
tl.to(
".hero-headline",
{
opacity: 0,
duration: 0.5,
ease: "power2.in",
},
0.3,
);
tl.fromTo(
".parallax-unzoom-grid",
{ "--pu-progress": 0 },
{
"--pu-progress": 1,
duration: 3.0,
ease: "power2.inOut",
},
0.5,
);
tl.fromTo(
".parallax-unzoom-card:not([data-pu-focus='true'])",
{ opacity: 0 },
{
opacity: 1,
duration: 1.5,
ease: "power2.out",
stagger: { each: 0.02, from: "center" },
},
2.0,
);
tl.to(
".focus-label",
{
opacity: 1,
duration: 1.0,
ease: "power2.out",
},
2.5,
);
window.__timelines = window.__timelines || {};
window.__timelines["parallax-unzoom-demo"] = tl;
})();
</script>
</div>
</body>
</html>
+103
View File
@@ -0,0 +1,103 @@
<!--
Parallax Unzoom — focus card scales down from full frame while siblings
parallax IN to form a grid. The reverse of parallax-zoom.
Pairs naturally with parallax-zoom: zoom INTO a card in scene 1, then
unzoom OUT of it in scene 2 to reveal a fresh grid underneath.
Usage:
1. Wrap your card grid with class="parallax-unzoom-grid".
2. Mark each card with class="parallax-unzoom-card".
3. Add data-pu-row="0|1|2|..." and data-pu-col="0|1|2|..." per card.
4. Mark the card that starts at full size with data-pu-focus="true".
5. Drive --pu-progress 0 → 1 on the .parallax-unzoom-grid from your
composition's paused GSAP timeline (see example at bottom).
Tunable CSS variables on .parallax-unzoom-grid:
- --pu-focus-scale (default 6) starting scale of the focus card
- --pu-sibling-push (default 900px) outward distance siblings start at
- --pu-sibling-fade (default 1) sibling opacity falloff (01)
-->
<style>
.parallax-unzoom-grid {
--pu-progress: 0;
--pu-focus-scale: 6;
--pu-sibling-push: 900px;
--pu-sibling-fade: 1;
position: relative;
}
.parallax-unzoom-card {
transform-origin: center center;
will-change: transform, opacity;
transform: translate3d(
calc(var(--pu-dx, 0px) * (1 - var(--pu-progress))),
calc(var(--pu-dy, 0px) * (1 - var(--pu-progress))),
0
)
scale(calc(0.7 + 0.3 * var(--pu-progress)));
opacity: calc(var(--pu-progress) * var(--pu-sibling-fade));
}
.parallax-unzoom-card[data-pu-focus="true"] {
z-index: 10;
transform: translate3d(0, 0, 0)
scale(calc(var(--pu-focus-scale) - (var(--pu-focus-scale) - 1) * var(--pu-progress)));
opacity: 1;
}
</style>
<script>
(function () {
const grids = document.querySelectorAll(".parallax-unzoom-grid");
grids.forEach((grid) => {
const cards = Array.from(grid.querySelectorAll(".parallax-unzoom-card"));
if (cards.length === 0) return;
let focal = cards.find((c) => c.dataset.puFocus === "true");
if (!focal) {
focal = cards[Math.floor(cards.length / 2)];
focal.dataset.puFocus = "true";
}
const focusRow = Number(focal.dataset.puRow ?? 0);
const focusCol = Number(focal.dataset.puCol ?? 0);
const push = parseFloat(getComputedStyle(grid).getPropertyValue("--pu-sibling-push")) || 900;
cards.forEach((card) => {
if (card === focal) return;
const row = Number(card.dataset.puRow ?? 0);
const col = Number(card.dataset.puCol ?? 0);
const dCol = col - focusCol;
const dRow = row - focusRow;
const mag = Math.hypot(dCol, dRow) || 1;
const nx = (dCol / mag) * push * mag;
const ny = (dRow / mag) * push * mag;
card.style.setProperty("--pu-dx", `${nx}px`);
card.style.setProperty("--pu-dy", `${ny}px`);
});
});
})();
</script>
<!--
Timeline integration example (paste into your composition's GSAP timeline):
tl.fromTo(
".parallax-unzoom-grid",
{ "--pu-progress": 0 },
{
"--pu-progress": 1,
duration: 2.4,
ease: "power2.inOut",
},
startTime, // start time in seconds
);
Chaining with parallax-zoom example:
// Scene 1 (0-3s): zoom IN to the focal card via parallax-zoom
// Scene 2 (3-6s): unzoom OUT of a NEW grid via parallax-unzoom,
// with the focal card pre-positioned to match scene 1
-->
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "parallax-unzoom",
"type": "hyperframes:component",
"title": "Parallax Unzoom",
"description": "Reveal transition — focus card scales down from full frame as siblings parallax in to form a grid (reverse of parallax-zoom)",
"tags": ["transition", "reveal", "unzoom", "parallax", "grid", "hero"],
"files": [
{
"path": "parallax-unzoom.html",
"target": "compositions/components/parallax-unzoom.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/parallax-unzoom.png"
}
}
+342
View File
@@ -0,0 +1,342 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Parallax Zoom — Demo</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@400;600;800;900&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 1920px;
height: 1080px;
overflow: hidden;
background: #1a1a20;
font-family: "Inter", system-ui, sans-serif;
}
.demo-canvas {
width: 1920px;
height: 1080px;
position: relative;
overflow: hidden;
}
.parallax-zoom-grid {
display: grid;
grid-template-columns: repeat(5, 360px);
grid-template-rows: repeat(3, 341px);
gap: 30px;
}
.parallax-zoom-card {
border-radius: 22px;
overflow: hidden;
position: relative;
box-shadow: 0 12px 36px rgba(20, 20, 30, 0.08);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
.card-glyph {
font-size: 92px;
font-weight: 800;
color: rgba(255, 255, 255, 0.92);
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
letter-spacing: -0.04em;
line-height: 1;
}
.pz-c0 {
background: linear-gradient(135deg, #3a8b6f, #1d4d3d);
}
.pz-c1 {
background: linear-gradient(135deg, #8b4a3a, #4a2418);
}
.pz-c2 {
background: linear-gradient(135deg, #c89868, #8a6038);
}
.pz-c3 {
background: linear-gradient(135deg, #3a6088, #1a2f4a);
}
.pz-c4 {
background: linear-gradient(135deg, #b03838, #6a1818);
}
.pz-c5 {
background: linear-gradient(135deg, #bcbcbc, #6e6e6e);
}
.pz-c6 {
background: linear-gradient(135deg, #8a5fa0, #4a2860);
}
.pz-c7-focus {
background: linear-gradient(135deg, #f5c93f, #d99a18);
}
.pz-c8 {
background: linear-gradient(135deg, #d8d8d8, #9a9a9a);
}
.pz-c9 {
background: linear-gradient(135deg, #5fa37e, #2a6a4a);
}
.pz-c10 {
background: linear-gradient(135deg, #d1b66a, #8a7028);
}
.pz-c11 {
background: linear-gradient(135deg, #4f7fc0, #1f3f80);
}
.pz-c12 {
background: linear-gradient(135deg, #5a3a8f, #2a1858);
}
.pz-c13 {
background: linear-gradient(135deg, #b85a3a, #6a2818);
}
.pz-c14 {
background: linear-gradient(135deg, #f0f0e8, #c8c8be);
}
.parallax-zoom-card[data-pz-focus="true"] {
grid-column: 3;
grid-row: 2;
}
.focus-label {
font-weight: 900;
font-size: 56px;
letter-spacing: -0.04em;
color: #1a1208;
text-align: center;
line-height: 1;
}
.focus-label small {
display: block;
font-size: 18px;
font-weight: 600;
letter-spacing: 0.18em;
margin-bottom: 14px;
color: rgba(26, 18, 8, 0.7);
}
.zoom-backdrop {
position: absolute;
inset: 0;
background: linear-gradient(135deg, #f5c93f, #d99a18);
opacity: 0;
z-index: 0;
}
.hero-headline {
position: absolute;
top: 80px;
left: 0;
width: 100%;
text-align: center;
font-size: 68px;
font-weight: 800;
letter-spacing: -0.03em;
color: #1a1a1a;
opacity: 0;
z-index: 20;
}
.parallax-zoom-grid {
--pz-progress: 0;
--pz-focus-scale: 6;
--pz-sibling-push: 900px;
--pz-sibling-fade: 1;
position: relative;
}
.parallax-zoom-card {
transform-origin: center center;
will-change: transform, opacity;
transform: translate3d(
calc(var(--pz-dx, 0px) * var(--pz-progress)),
calc(var(--pz-dy, 0px) * var(--pz-progress)),
0
)
scale(calc(1 - var(--pz-progress) * 0.4));
}
.parallax-zoom-card[data-pz-focus="true"] {
z-index: 10;
transform: translate3d(0, 0, 0)
scale(calc(1 + var(--pz-progress) * (var(--pz-focus-scale) - 1)));
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="parallax-zoom-demo"
data-width="1920"
data-height="1080"
data-duration="6"
data-start="0"
>
<div class="demo-canvas">
<div class="zoom-backdrop"></div>
<div class="hero-headline">Inspired by how people discover.</div>
<div class="parallax-zoom-grid">
<div class="parallax-zoom-card pz-c0" data-pz-row="0" data-pz-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c1" data-pz-row="0" data-pz-col="1">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c2" data-pz-row="0" data-pz-col="2">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c3" data-pz-row="0" data-pz-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c4" data-pz-row="0" data-pz-col="4">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c5" data-pz-row="1" data-pz-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c6" data-pz-row="1" data-pz-col="1">
<span class="card-glyph"></span>
</div>
<div
class="parallax-zoom-card pz-c7-focus"
data-pz-row="1"
data-pz-col="2"
data-pz-focus="true"
>
<div class="focus-label">
<small>EBAY PLAYBOOK</small>
DISCOVER
</div>
</div>
<div class="parallax-zoom-card pz-c8" data-pz-row="1" data-pz-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c9" data-pz-row="1" data-pz-col="4">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c10" data-pz-row="2" data-pz-col="0">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c11" data-pz-row="2" data-pz-col="1">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c12" data-pz-row="2" data-pz-col="2">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c13" data-pz-row="2" data-pz-col="3">
<span class="card-glyph"></span>
</div>
<div class="parallax-zoom-card pz-c14" data-pz-row="2" data-pz-col="4">
<span class="card-glyph" style="color: rgba(40, 40, 40, 0.7)"></span>
</div>
</div>
</div>
<script>
(function () {
const grids = document.querySelectorAll(".parallax-zoom-grid");
grids.forEach((grid) => {
const cards = Array.from(grid.querySelectorAll(".parallax-zoom-card"));
if (cards.length === 0) return;
let focal = cards.find((c) => c.dataset.pzFocus === "true");
if (!focal) {
focal = cards[Math.floor(cards.length / 2)];
focal.dataset.pzFocus = "true";
}
const focusRow = Number(focal.dataset.pzRow ?? 0);
const focusCol = Number(focal.dataset.pzCol ?? 0);
const push =
parseFloat(getComputedStyle(grid).getPropertyValue("--pz-sibling-push")) || 900;
cards.forEach((card) => {
if (card === focal) return;
const row = Number(card.dataset.pzRow ?? 0);
const col = Number(card.dataset.pzCol ?? 0);
const dCol = col - focusCol;
const dRow = row - focusRow;
const mag = Math.hypot(dCol, dRow) || 1;
const nx = (dCol / mag) * push * mag;
const ny = (dRow / mag) * push * mag;
card.style.setProperty("--pz-dx", `${nx}px`);
card.style.setProperty("--pz-dy", `${ny}px`);
});
});
const tl = gsap.timeline({ paused: true });
tl.to(
".parallax-zoom-card",
{
opacity: 1,
duration: 0.5,
ease: "power2.out",
stagger: { each: 0.03, from: "center" },
},
0,
);
tl.fromTo(
".parallax-zoom-grid",
{ "--pz-progress": 0 },
{
"--pz-progress": 1,
duration: 3.0,
ease: "power2.inOut",
},
1.7,
);
tl.to(
".parallax-zoom-card:not([data-pz-focus='true'])",
{
opacity: 0,
duration: 1.0,
ease: "power2.in",
},
3.5,
);
tl.to(
".focus-label",
{
opacity: 0,
duration: 1.0,
ease: "power2.in",
},
2.5,
);
tl.to(
".hero-headline",
{
opacity: 1,
duration: 0.7,
ease: "power2.out",
},
4.2,
);
window.__timelines = window.__timelines || {};
window.__timelines["parallax-zoom-demo"] = tl;
})();
</script>
</div>
</body>
</html>
+99
View File
@@ -0,0 +1,99 @@
<!--
Parallax Zoom — center card scales up while siblings parallax outward.
Inspired by the eBay Playbook hero transition: a grid of cards collapses
as the focal card grows to fill the frame.
Usage:
1. Wrap your card grid with class="parallax-zoom-grid".
2. Mark each card with class="parallax-zoom-card".
3. Add data-pz-row="0|1|2|..." and data-pz-col="0|1|2|..." per card
so the snippet knows each card's grid offset.
4. Mark the focal card with data-pz-focus="true" — this is the one
that scales up to fill the frame.
5. Drive --pz-progress 0 → 1 on the .parallax-zoom-grid from your
composition's paused GSAP timeline (see example at bottom).
Tunable CSS variables on .parallax-zoom-grid:
- --pz-focus-scale (default 6) maximum scale of the focused card
- --pz-sibling-push (default 900px) outward translation strength
- --pz-sibling-fade (default 1) sibling opacity falloff (01)
-->
<style>
.parallax-zoom-grid {
--pz-progress: 0;
--pz-focus-scale: 6;
--pz-sibling-push: 900px;
--pz-sibling-fade: 1;
position: relative;
}
.parallax-zoom-card {
transform-origin: center center;
will-change: transform, opacity;
transform: translate3d(
calc(var(--pz-dx, 0px) * var(--pz-progress)),
calc(var(--pz-dy, 0px) * var(--pz-progress)),
0
)
scale(calc(1 - var(--pz-progress) * 0.4));
opacity: calc(1 - var(--pz-progress) * var(--pz-sibling-fade));
}
.parallax-zoom-card[data-pz-focus="true"] {
z-index: 10;
transform: translate3d(0, 0, 0)
scale(calc(1 + var(--pz-progress) * (var(--pz-focus-scale) - 1)));
opacity: 1;
}
</style>
<script>
(function () {
const grids = document.querySelectorAll(".parallax-zoom-grid");
grids.forEach((grid) => {
const cards = Array.from(grid.querySelectorAll(".parallax-zoom-card"));
if (cards.length === 0) return;
let focal = cards.find((c) => c.dataset.pzFocus === "true");
if (!focal) {
focal = cards[Math.floor(cards.length / 2)];
focal.dataset.pzFocus = "true";
}
const focusRow = Number(focal.dataset.pzRow ?? 0);
const focusCol = Number(focal.dataset.pzCol ?? 0);
const push = parseFloat(getComputedStyle(grid).getPropertyValue("--pz-sibling-push")) || 900;
cards.forEach((card) => {
if (card === focal) return;
const row = Number(card.dataset.pzRow ?? 0);
const col = Number(card.dataset.pzCol ?? 0);
const dCol = col - focusCol;
const dRow = row - focusRow;
const mag = Math.hypot(dCol, dRow) || 1;
const nx = (dCol / mag) * push * mag;
const ny = (dRow / mag) * push * mag;
card.style.setProperty("--pz-dx", `${nx}px`);
card.style.setProperty("--pz-dy", `${ny}px`);
});
});
})();
</script>
<!--
Timeline integration example (paste into your composition's GSAP timeline):
tl.fromTo(
".parallax-zoom-grid",
{ "--pz-progress": 0 },
{
"--pz-progress": 1,
duration: 2.4,
ease: "power2.inOut",
},
1.0, // start time in seconds
);
-->
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "parallax-zoom",
"type": "hyperframes:component",
"title": "Parallax Zoom",
"description": "Center card scales up to fill the frame while siblings parallax outward — inspired by the eBay Playbook hero transition",
"tags": ["transition", "zoom", "parallax", "grid", "hero"],
"files": [
{
"path": "parallax-zoom.html",
"target": "compositions/components/parallax-zoom.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/parallax-zoom.png"
}
}
+162
View File
@@ -0,0 +1,162 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Shimmer Sweep — Demo</title>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="shimmer-sweep-demo"
data-width="1920"
data-height="1080"
data-duration="6"
>
<div class="demo-canvas">
<!-- Example 1: Large heading -->
<div class="shimmer-sweep-target" style="--shimmer-color: rgba(255, 255, 255, 0.5)">
<div
class="demo-heading"
style="font-size: 120px; font-weight: 800; color: #ffffff; opacity: 0"
>
AI-Powered
</div>
</div>
<!-- Example 2: Subheading -->
<div class="shimmer-sweep-target" style="--shimmer-color: rgba(100, 200, 255, 0.4)">
<div
class="demo-subheading"
style="font-size: 48px; font-weight: 400; color: #888888; opacity: 0"
>
Video generation, reimagined
</div>
</div>
<!-- Example 3: Pill badge -->
<div
class="shimmer-sweep-target"
style="--shimmer-color: rgba(255, 255, 255, 0.3); --shimmer-width: 30%"
>
<div
class="demo-pill"
style="
background: linear-gradient(135deg, #1a1a2e, #2a2a4e);
border: 1px solid #333;
border-radius: 100px;
padding: 16px 40px;
font-size: 20px;
color: #aaa;
opacity: 0;
"
>
Try it free →
</div>
</div>
</div>
<!-- Shimmer Sweep component -->
<style>
.demo-canvas {
width: 1920px;
height: 1080px;
background: #0a0a0f;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 60px;
font-family: "Inter", sans-serif;
}
.shimmer-sweep-target {
position: relative;
display: inline-block;
}
.shimmer-sweep-target .shimmer-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: linear-gradient(
var(--shimmer-angle, 120deg),
transparent 0%,
transparent calc(var(--shimmer-pos, -20%) - var(--shimmer-width, 20%) / 2),
var(--shimmer-color, rgba(255, 255, 255, 0.6)) var(--shimmer-pos, -20%),
transparent calc(var(--shimmer-pos, -20%) + var(--shimmer-width, 20%) / 2),
transparent 100%
);
mix-blend-mode: overlay;
}
</style>
<script>
(function () {
// Inject shimmer masks
document.querySelectorAll(".shimmer-sweep-target").forEach((el) => {
if (!el.querySelector(".shimmer-mask")) {
const mask = document.createElement("div");
mask.className = "shimmer-mask";
el.appendChild(mask);
}
});
const tl = gsap.timeline({ paused: true });
// Fade in elements
tl.to(".demo-heading", { opacity: 1, duration: 0.6, ease: "power2.out" }, 0.3);
tl.to(".demo-subheading", { opacity: 1, duration: 0.6, ease: "power2.out" }, 0.6);
tl.to(".demo-pill", { opacity: 1, duration: 0.6, ease: "power2.out" }, 0.9);
// First shimmer sweep across all targets
tl.fromTo(
".shimmer-sweep-target",
{ "--shimmer-pos": "-20%" },
{
"--shimmer-pos": "120%",
duration: 1.2,
ease: "power2.inOut",
stagger: 0.15,
},
1.5,
);
// Second sweep
tl.fromTo(
".shimmer-sweep-target",
{ "--shimmer-pos": "-20%" },
{
"--shimmer-pos": "120%",
duration: 1.0,
ease: "power2.inOut",
stagger: 0.1,
},
3.5,
);
window.__timelines = window.__timelines || {};
window.__timelines["shimmer-sweep-demo"] = tl;
})();
</script>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "shimmer-sweep",
"type": "hyperframes:component",
"title": "Shimmer Sweep",
"description": "Animated light sweep across text or elements using a CSS gradient mask — ideal for AI accents and premium reveals",
"tags": ["text", "shimmer", "highlight", "effect"],
"files": [
{
"path": "shimmer-sweep.html",
"target": "compositions/components/shimmer-sweep.html",
"type": "hyperframes:snippet"
}
],
"preview": {
"poster": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/shimmer-sweep.png"
}
}
+66
View File
@@ -0,0 +1,66 @@
<!--
Shimmer Sweep — animated light pass across text or elements.
Usage: place this snippet AFTER your .shimmer-sweep-target elements.
Wrap your text element with class="shimmer-sweep-target" and
paste this snippet into your composition. The GSAP timeline drives
the sweep position for deterministic frame-by-frame rendering.
Customize:
- --shimmer-color: highlight color (default rgba(255,255,255,0.6))
- --shimmer-width: highlight band width as % (default 20%)
- --shimmer-angle: sweep angle (default 120deg)
- Timeline duration/ease: adjust in the script below
-->
<style>
.shimmer-sweep-target {
position: relative;
display: inline-block;
}
.shimmer-sweep-target .shimmer-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
background: linear-gradient(
var(--shimmer-angle, 120deg),
transparent 0%,
transparent calc(var(--shimmer-pos, -20%) - var(--shimmer-width, 20%) / 2),
var(--shimmer-color, rgba(255, 255, 255, 0.6)) var(--shimmer-pos, -20%),
transparent calc(var(--shimmer-pos, -20%) + var(--shimmer-width, 20%) / 2),
transparent 100%
);
mix-blend-mode: overlay;
}
</style>
<script>
(function () {
// Auto-inject .shimmer-mask into every .shimmer-sweep-target
document.querySelectorAll(".shimmer-sweep-target").forEach((el) => {
if (!el.querySelector(".shimmer-mask")) {
const mask = document.createElement("div");
mask.className = "shimmer-mask";
el.appendChild(mask);
}
});
})();
</script>
<!--
Timeline integration example (add to your composition's GSAP timeline):
// Single sweep from left to right
tl.fromTo(".shimmer-sweep-target", {
"--shimmer-pos": "-20%",
}, {
"--shimmer-pos": "120%",
duration: 1.2,
ease: "power2.inOut",
stagger: 0.15,
}, startTime);
-->
+286
View File
@@ -0,0 +1,286 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<title>Texture Mask Text - Demo</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #111;
}
</style>
</head>
<body>
<div
id="demo"
data-composition-id="texture-mask-text-demo"
data-width="1920"
data-height="1080"
data-duration="5"
data-start="0"
>
<div class="demo-canvas">
<div class="header">
<div class="title">Texture Mask Text</div>
<div class="count">66 ambientCG luminance masks</div>
</div>
<div class="groups" id="texture-groups"></div>
</div>
<style>
#demo {
width: 1920px;
height: 1080px;
overflow: hidden;
background: #111;
color: #fff;
font-family:
Arial Black,
Arial,
sans-serif;
}
.hf-texture-text {
color: #fff;
-webkit-mask-size: var(--mask-size, cover);
mask-size: var(--mask-size, cover);
-webkit-mask-position: var(--mask-position, center);
mask-position: var(--mask-position, center);
-webkit-mask-mode: luminance;
mask-mode: luminance;
filter: contrast(var(--mask-contrast, 1));
}
.demo-canvas {
width: 1920px;
height: 1080px;
background: #111;
padding: 42px 44px 38px;
}
.header {
height: 84px;
display: flex;
align-items: flex-start;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
margin-bottom: 18px;
}
.title {
font-size: 34px;
line-height: 1;
letter-spacing: 0;
text-transform: uppercase;
}
.count {
font-family: Arial, sans-serif;
font-size: 18px;
line-height: 1;
letter-spacing: 0;
color: rgba(255, 255, 255, 0.55);
text-transform: uppercase;
}
.groups {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 1fr);
gap: 22px 24px;
width: 1832px;
height: 898px;
}
.group-panel {
min-width: 0;
min-height: 0;
overflow: hidden;
}
.group-header {
height: 34px;
display: flex;
align-items: flex-start;
justify-content: space-between;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 10px;
}
.group-name {
font-family: Arial, sans-serif;
font-size: 13px;
line-height: 1;
letter-spacing: 0;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.74);
}
.group-count {
font-family: Arial, sans-serif;
font-size: 10px;
line-height: 1;
letter-spacing: 0;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.52);
}
.group-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px 10px;
}
.cell {
min-width: 0;
height: 66px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 9px;
overflow: hidden;
}
.label {
width: 100%;
font-family: Arial, sans-serif;
font-size: 11px;
line-height: 1.1;
letter-spacing: 0;
text-transform: uppercase;
text-align: center;
color: rgba(255, 255, 255, 0.5);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.shadow-wrap {
width: 100%;
filter: drop-shadow(2px 3px 4px rgba(0, 0, 0, 0.46));
}
.sample {
width: 100%;
font-family:
Impact,
Arial Black,
Arial,
sans-serif;
font-size: 31px;
line-height: 0.9;
letter-spacing: 0;
text-transform: uppercase;
text-align: center;
white-space: nowrap;
}
</style>
<script>
const textureMaskTextGroups = [
[
"Masonry",
"brick|Brick|MORTAR,bricks-104|Bricks 104|BRICK,bricks-102|Bricks 102|BRICK,bricks-101|Bricks 101|BRICK,bricks-075-a|Bricks 075 A|BRICK,concrete|Concrete|SCORED,concrete-034|Concrete 034|CONCRETE,concrete-047-a|Concrete 047 A|CONCRETE,concrete-046|Concrete 046|CONCRETE,concrete-042-a|Concrete 042 A|CONCRETE,plaster-001|Plaster 001|PLASTER,painted-plaster-017|Painted Plaster 017|PLASTER",
],
[
"Stone",
"rock|Rock|RUINED,rock-063|Rock 063|ROCK,rock-058|Rock 058|ROCK,onyx|Onyx|VEINED,marble-012|Marble 012|MARBLE,marble-016|Marble 016|MARBLE,travertine-009|Travertine 009|STONE,paving-stones-150|Paving Stones 150|STONE,paving-stones-138|Paving Stones 138|STONE,tiles-138|Tiles 138|TILE",
],
[
"Ground / Road",
"ground-103|Ground 103|GROUND,ground-037|Ground 037|GROUND,ground-054|Ground 054|GROUND,ground-104|Ground 104|GROUND,ground-068|Ground 068|GROUND,ground-080|Ground 080|GROUND,road-012-a|Road 012 A|ROAD,road-008-a|Road 008 A|ROAD,road-007|Road 007|ROAD,road-013-a|Road 013 A|ROAD,road-012-b|Road 012 B|ROAD,road-009-c|Road 009 C|ROAD,asphalt-031|Asphalt 031|ASPHALT",
],
[
"Wood",
"wood|Wood|SLICED,wood-094|Wood 094|WOOD,wood-092|Wood 092|WOOD,wood-051|Wood 051|WOOD,wood-066|Wood 066|WOOD,wood-049|Wood 049|WOOD,wood-058|Wood 058|WOOD,wood-floor-051|Wood Floor 051|FLOOR,wood-floor-064|Wood Floor 064|FLOOR,wood-floor-070|Wood Floor 070|FLOOR,bark-014|Bark 014|BARK",
],
[
"Metal",
"metal|Metal|FADING,metal-049-a|Metal 049 A|METAL,metal-055-a|Metal 055 A|METAL,metal-046-b|Metal 046 B|METAL,metal-061-b|Metal 061 B|METAL,metal-048-a|Metal 048 A|METAL,metal-032|Metal 032|METAL,metal-041-a|Metal 041 A|METAL,metal-038|Metal 038|METAL,diamond-plate-009|Diamond Plate 009|PLATE",
],
[
"Organic / Soft",
"lava|Lava|MOLTEN,grass-005|Grass 005|GRASS,grass-001|Grass 001|GRASS,grass-004|Grass 004|GRASS,carpet|Carpet|WOVEN,fabric-083|Fabric 083|FABRIC,snow|Snow|THAW,snow-015|Snow 015|SNOW,leather-037|Leather 037|LEATHER,fabric-080|Fabric 080|FABRIC",
],
];
const textureGroups = document.getElementById("texture-groups");
for (const [groupName, rows] of textureMaskTextGroups) {
const items = rows.split(",").map((row) => {
const [slug, title, sample] = row.split("|");
return { slug, title, sample };
});
const panel = document.createElement("section");
panel.className = "group-panel";
const groupHeader = document.createElement("div");
groupHeader.className = "group-header";
const name = document.createElement("div");
name.className = "group-name";
name.textContent = groupName;
const count = document.createElement("div");
count.className = "group-count";
count.textContent = `${items.length} masks`;
groupHeader.append(name, count);
const groupGrid = document.createElement("div");
groupGrid.className = "group-grid";
for (const item of items) {
const cell = document.createElement("div");
cell.className = "cell";
const label = document.createElement("div");
label.className = "label";
label.textContent = item.title;
const shadowWrap = document.createElement("div");
shadowWrap.className = "shadow-wrap";
const sample = document.createElement("div");
sample.className = "hf-texture-text sample";
sample.style.webkitMaskImage = `url("masks/${item.slug}.png")`;
sample.style.maskImage = `url("masks/${item.slug}.png")`;
sample.textContent = item.sample;
shadowWrap.appendChild(sample);
cell.append(label, shadowWrap);
groupGrid.appendChild(cell);
}
panel.append(groupHeader, groupGrid);
textureGroups.appendChild(panel);
}
window.__timelines = window.__timelines || {};
const textureMaskTextTimeline = {
play() {},
pause() {},
seek() {},
time() {
return 0;
},
duration() {
return 5;
},
add() {},
paused() {},
set() {},
};
window.__timelines["texture-mask-text-demo"] = textureMaskTextTimeline;
</script>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Some files were not shown because too many files have changed in this diff Show More