Merge pull request #927 from heygen-com/fix/caption-polish

fix(registry): polish caption timing and add highlight style
This commit is contained in:
Miguel Ángel
2026-05-18 03:06:57 +02:00
committed by GitHub
27 changed files with 691 additions and 518 deletions
@@ -9,7 +9,7 @@ RGB chromatic aberration with CRT scanline overlay
`captions` `caption-style` `glitch` `cyber` `tech` `captions` `caption-style` `glitch` `cyber` `tech`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-glitch-rgb.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-glitch-rgb.png" autoPlay muted loop playsInline /> <video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-glitch-rgb.mp4?v=1779066308" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-glitch-rgb.png" autoPlay muted loop playsInline />
## Install ## Install
@@ -0,0 +1,38 @@
---
title: "Highlight"
description: "Red background sweep behind each active word, TikTok-style"
---
# Highlight
Red background sweep behind each active word, TikTok-style
`captions` `caption-style` `highlight` `social` `tiktok`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-highlight.mp4?v=1779066308" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-highlight.png" autoPlay muted loop playsInline />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add caption-highlight
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Component |
## Files
| File | Target | Type |
| --- | --- | --- |
| `caption-highlight.html` | `compositions/components/caption-highlight.html` | hyperframes:snippet |
## Usage
Open `compositions/components/caption-highlight.html` and paste its contents into your composition. See the comment header in the file for detailed instructions.
@@ -1,38 +0,0 @@
---
title: "Typewriter"
description: "Green terminal character-by-character reveal with blinking cursor"
---
# Typewriter
Green terminal character-by-character reveal with blinking cursor
`captions` `caption-style` `terminal` `typewriter` `hacker`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-typewriter.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-typewriter.png" autoPlay muted loop playsInline />
## Install
<CodeGroup>
```bash Terminal
npx hyperframes add caption-typewriter
```
</CodeGroup>
## Details
| Property | Value |
| --- | --- |
| Type | Component |
## Files
| File | Target | Type |
| --- | --- | --- |
| `caption-typewriter.html` | `compositions/components/caption-typewriter.html` | hyperframes:snippet |
## Usage
Open `compositions/components/caption-typewriter.html` and paste its contents into your composition. See the comment header in the file for detailed instructions.
@@ -9,7 +9,7 @@ Elegant font-weight transition between caption lines
`captions` `caption-style` `minimal` `typography` `captions` `caption-style` `minimal` `typography`
<video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-weight-shift.mp4" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-weight-shift.png" autoPlay muted loop playsInline /> <video className="w-full aspect-video rounded-xl object-cover bg-zinc-100 dark:bg-zinc-800" src="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-weight-shift.mp4?v=1779066308" poster="https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-weight-shift.png" autoPlay muted loop playsInline />
## Install ## Install
+1 -1
View File
@@ -103,6 +103,7 @@
"catalog/components/caption-emoji-pop", "catalog/components/caption-emoji-pop",
"catalog/components/caption-glitch-rgb", "catalog/components/caption-glitch-rgb",
"catalog/components/caption-gradient-fill", "catalog/components/caption-gradient-fill",
"catalog/components/caption-highlight",
"catalog/components/caption-kinetic-slam", "catalog/components/caption-kinetic-slam",
"catalog/components/caption-matrix-decode", "catalog/components/caption-matrix-decode",
"catalog/components/caption-neon-accent", "catalog/components/caption-neon-accent",
@@ -111,7 +112,6 @@
"catalog/components/caption-particle-burst", "catalog/components/caption-particle-burst",
"catalog/components/caption-pill-karaoke", "catalog/components/caption-pill-karaoke",
"catalog/components/caption-texture-lava", "catalog/components/caption-texture-lava",
"catalog/components/caption-typewriter",
"catalog/components/caption-weight-shift" "catalog/components/caption-weight-shift"
] ]
}, },
+15 -15
View File
@@ -114,6 +114,21 @@
"href": "/catalog/components/caption-gradient-fill", "href": "/catalog/components/caption-gradient-fill",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-gradient-fill.png" "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-gradient-fill.png"
}, },
{
"name": "caption-highlight",
"type": "component",
"title": "Highlight",
"description": "Red background sweep behind each active word, TikTok-style",
"tags": [
"captions",
"caption-style",
"highlight",
"social",
"tiktok"
],
"href": "/catalog/components/caption-highlight",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-highlight.png"
},
{ {
"name": "caption-kinetic-slam", "name": "caption-kinetic-slam",
"type": "component", "type": "component",
@@ -233,21 +248,6 @@
"href": "/catalog/components/caption-texture-lava", "href": "/catalog/components/caption-texture-lava",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-texture-lava.png" "preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-texture-lava.png"
}, },
{
"name": "caption-typewriter",
"type": "component",
"title": "Typewriter",
"description": "Green terminal character-by-character reveal with blinking cursor",
"tags": [
"captions",
"caption-style",
"terminal",
"typewriter",
"hacker"
],
"href": "/catalog/components/caption-typewriter",
"preview": "https://static.heygen.ai/hyperframes-oss/docs/images/catalog/components/caption-typewriter.png"
},
{ {
"name": "caption-weight-shift", "name": "caption-weight-shift",
"type": "component", "type": "component",
@@ -257,15 +257,32 @@ async function buildFontFaceCss(
const unresolved: string[] = []; const unresolved: string[] = [];
for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) { for (const [normalizedFamily, originalCaseFamily] of requestedFamilies) {
// Path 1: pre-bundled fonts via FONT_ALIASES // Path 1: pre-bundled fonts via FONT_ALIASES — emit embedded faces,
// then fetch from Google Fonts to fill any weights not in the bundle.
const canonicalKey = FONT_ALIASES[normalizedFamily]; const canonicalKey = FONT_ALIASES[normalizedFamily];
if (canonicalKey) { if (canonicalKey) {
const canonical = CANONICAL_FONTS[canonicalKey]; const canonical = CANONICAL_FONTS[canonicalKey];
if (!canonical) continue; if (!canonical) continue;
const coveredWeights = new Set<string>();
for (const face of canonical.faces) { for (const face of canonical.faces) {
const style = face.style || "normal"; const style = face.style || "normal";
const src = fontDataUri(canonical.packageName, face.weight, style); const src = fontDataUri(canonical.packageName, face.weight, style);
rules.push(buildFontFaceRule(originalCaseFamily, src, face.weight, style)); rules.push(buildFontFaceRule(originalCaseFamily, src, face.weight, style));
coveredWeights.add(`${face.weight}:${style}`);
}
// Fetch all weights from Google Fonts and add any that aren't
// already covered by the embedded bundle. This ensures that
// compositions requesting e.g. wght@200 get that weight even
// if the bundle only ships 400/700/900.
const googleFaces = await fetchGoogleFont(originalCaseFamily, options);
for (const face of googleFaces) {
const key = `${face.weight}:${face.style}`;
if (!coveredWeights.has(key)) {
rules.push(buildFontFaceRule(originalCaseFamily, face.dataUri, face.weight, face.style));
coveredWeights.add(key);
}
} }
continue; continue;
} }
@@ -169,7 +169,7 @@
var ws = pair[0], var ws = pair[0],
we = pair[1]; we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; 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); var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
}); });
@@ -210,13 +210,13 @@
tl.to( tl.to(
wordEl, wordEl,
{ clipPath: "inset(0 0% 0 0)", duration: 0.15, ease: "power3.out" }, { clipPath: "inset(0 0% 0 0)", duration: 0.3, ease: "power2.out" },
w.start, w.start,
); );
if (isKW) { if (isKW) {
tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1); tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1);
} }
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.12 }, w.end); tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.2 }, w.end);
}); });
var wordEls = grp.querySelectorAll(".wp-word"); var wordEls = grp.querySelectorAll(".wp-word");
@@ -224,7 +224,7 @@
wordEls, wordEls,
{ {
clipPath: "inset(0 0% 0 100%)", clipPath: "inset(0 0% 0 100%)",
duration: 0.14, duration: 0.25,
stagger: 0.04, stagger: 0.04,
ease: "power2.in", ease: "power2.in",
}, },
+4 -4
View File
@@ -169,7 +169,7 @@
var ws = pair[0], var ws = pair[0],
we = pair[1]; we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; 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); var gEnd = Math.min(WORDS[we].end + 0.5, nextStart - 0.05);
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd };
}); });
@@ -210,13 +210,13 @@
tl.to( tl.to(
wordEl, wordEl,
{ clipPath: "inset(0 0% 0 0)", duration: 0.15, ease: "power3.out" }, { clipPath: "inset(0 0% 0 0)", duration: 0.3, ease: "power2.out" },
w.start, w.start,
); );
if (isKW) { if (isKW) {
tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1); tl.to(wordEl, { color: "#FFD700", duration: 0.05 }, w.start + 0.1);
} }
tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.12 }, w.end); tl.to(wordEl, { color: "rgba(255,255,255,0.4)", duration: 0.2 }, w.end);
}); });
var wordEls = grp.querySelectorAll(".wp-word"); var wordEls = grp.querySelectorAll(".wp-word");
@@ -224,7 +224,7 @@
wordEls, wordEls,
{ {
clipPath: "inset(0 0% 0 100%)", clipPath: "inset(0 0% 0 100%)",
duration: 0.14, duration: 0.25,
stagger: 0.04, stagger: 0.04,
ease: "power2.in", ease: "power2.in",
}, },
@@ -12,5 +12,5 @@
"type": "hyperframes:snippet" "type": "hyperframes:snippet"
} }
], ],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-clip-wipe/preview.mp4?v=1779051416" "preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-clip-wipe/preview.mp4?v=1779057707"
} }
+133 -101
View File
@@ -25,7 +25,7 @@
overflow: hidden; overflow: hidden;
background: #0a0a0a; background: #0a0a0a;
} }
#glitch-rgb { #caption-glitch-rgb {
position: relative; position: relative;
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
@@ -43,8 +43,13 @@
.gl-overlay { .gl-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1; 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; pointer-events: none;
} }
.gl-scanlines { .gl-scanlines {
@@ -56,9 +61,10 @@
to bottom, to bottom,
transparent 0px, transparent 0px,
transparent 3px, transparent 3px,
rgba(0, 0, 0, 0.08) 3px, rgba(0, 0, 0, 0.16) 3px,
rgba(0, 0, 0, 0.08) 4px rgba(0, 0, 0, 0.16) 4px
); );
opacity: 0.55;
} }
#gl-container { #gl-container {
position: absolute; position: absolute;
@@ -75,7 +81,7 @@
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
gap: 24px; gap: 14px;
padding: 0 100px; padding: 0 100px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
@@ -85,17 +91,18 @@
font-weight: 700; font-weight: 700;
font-size: 88px; font-size: 88px;
text-transform: uppercase; text-transform: uppercase;
color: rgba(180, 230, 255, 0.45); color: #ffffff;
display: inline-block; display: inline-block;
letter-spacing: 0.05em; letter-spacing: 0.03em;
line-height: 1; line-height: 0.96;
will-change: transform, text-shadow, color; text-shadow: 0 5px 18px rgba(0, 0, 0, 0.52);
will-change: transform, text-shadow;
} }
</style> </style>
</head> </head>
<body> <body>
<div <div
id="glitch-rgb" id="caption-glitch-rgb"
data-composition-id="caption-glitch-rgb" data-composition-id="caption-glitch-rgb"
data-start="0" data-start="0"
data-duration="8" data-duration="8"
@@ -121,30 +128,7 @@
(function () { (function () {
window.__timelines = window.__timelines || {}; window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas"); var TRANSCRIPT = [
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: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 }, { text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 }, { text: "video", start: 0.55, end: 0.85 },
@@ -175,27 +159,69 @@
{ text: "cinema.", start: 7.3, end: 7.7 }, { text: "cinema.", start: 7.3, end: 7.7 },
]; ];
// Keywords get white + glow on activation var _fitCanvas = document.createElement("canvas");
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema. 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 RAW_GROUPS = [ function mulberry32(seed) {
[0, 3], return function () {
[4, 7], seed |= 0;
[8, 8], seed = (seed + 0x6d2b79f5) | 0;
[9, 12], var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
[13, 15], t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
[16, 16], return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
[17, 19], };
[20, 22], }
[23, 24],
[25, 27], 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 GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0], var ws = pair[0];
we = pair[1]; var we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; var nextStart =
var gEnd = Math.min(WORDS[we].end + 0.35, nextStart - 0.05); gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : 8.0;
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; 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 BASE_FONT = 88;
@@ -203,7 +229,7 @@
var tl = gsap.timeline({ paused: true }); var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) { GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1); var groupWords = TRANSCRIPT.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div"); var grp = document.createElement("div");
grp.className = "gl-group"; grp.className = "gl-group";
grp.id = "gl-grp-" + gi; grp.id = "gl-grp-" + gi;
@@ -230,77 +256,83 @@
tl.set(grp, { visibility: "visible" }, g.start); tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo( tl.fromTo(
grp, grp,
{ opacity: 0, x: -8 }, { opacity: 0 },
{ opacity: 1, x: 0, duration: 0.18, ease: "power3.out" }, { opacity: 1, duration: 0.12, ease: "power2.out" },
g.start, g.start,
); );
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var wordEl = document.getElementById("gl-w-" + wi); var wordEl = document.getElementById("gl-w-" + wi);
var isKW = KEYWORDS.has(wi); var plan = glitchPlan(w, TRANSCRIPT[wi + 1]);
var rng = mulberry32(wi * 777 + 13); var rng = mulberry32(wi * 777 + 13);
var j1 = (rng() - 0.5) * 10; var travel = -(8 + rng() * 8);
var j2 = (rng() - 0.5) * 8; var shadowMag = 5 + rng() * 7;
var j3 = (rng() - 0.5) * 6;
var shadowMag = 3 + rng() * 4;
var sm = shadowMag.toFixed(1); 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( tl.to(
wordEl, wordEl,
{ {
x: j1, x: travel,
color: "#ffffff", textShadow:
textShadow: sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff", sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: 0.045, duration: plan.d1,
ease: "none", ease: "none",
}, },
w.start, w.start,
); );
tl.to(
wordEl,
{
x: j2,
textShadow:
"-" +
(shadowMag * 0.7).toFixed(1) +
"px 0 #ff003c, " +
(shadowMag * 0.7).toFixed(1) +
"px 0 #00e5ff",
duration: 0.038,
ease: "none",
},
w.start + 0.045,
);
tl.to(
wordEl,
{
x: j3,
textShadow: sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff",
duration: 0.03,
ease: "none",
},
w.start + 0.083,
);
tl.to( tl.to(
wordEl, wordEl,
{ {
x: 0, x: 0,
textShadow: "none", textShadow: "0 5px 18px rgba(0,0,0,0.52)",
color: isKW ? "#ffffff" : "#00e5ff", duration: plan.d2,
duration: 0.05, ease: "power3.out",
ease: "power2.out",
}, },
w.start + 0.113, w.start + plan.d1,
);
tl.to(
wordEl,
{ color: "rgba(180,230,255,0.4)", textShadow: "none", duration: 0.12 },
w.end,
); );
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, x: 8, duration: 0.15 }, g.end - 0.15); 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.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
+133 -101
View File
@@ -25,7 +25,7 @@
overflow: hidden; overflow: hidden;
background: #0a0a0a; background: #0a0a0a;
} }
#glitch-rgb { #caption-glitch-rgb {
position: relative; position: relative;
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
@@ -43,8 +43,13 @@
.gl-overlay { .gl-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(0, 0, 0, 0.45);
z-index: 1; 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; pointer-events: none;
} }
.gl-scanlines { .gl-scanlines {
@@ -56,9 +61,10 @@
to bottom, to bottom,
transparent 0px, transparent 0px,
transparent 3px, transparent 3px,
rgba(0, 0, 0, 0.08) 3px, rgba(0, 0, 0, 0.16) 3px,
rgba(0, 0, 0, 0.08) 4px rgba(0, 0, 0, 0.16) 4px
); );
opacity: 0.55;
} }
#gl-container { #gl-container {
position: absolute; position: absolute;
@@ -75,7 +81,7 @@
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
gap: 24px; gap: 14px;
padding: 0 100px; padding: 0 100px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
@@ -85,17 +91,18 @@
font-weight: 700; font-weight: 700;
font-size: 88px; font-size: 88px;
text-transform: uppercase; text-transform: uppercase;
color: rgba(180, 230, 255, 0.45); color: #ffffff;
display: inline-block; display: inline-block;
letter-spacing: 0.05em; letter-spacing: 0.03em;
line-height: 1; line-height: 0.96;
will-change: transform, text-shadow, color; text-shadow: 0 5px 18px rgba(0, 0, 0, 0.52);
will-change: transform, text-shadow;
} }
</style> </style>
</head> </head>
<body> <body>
<div <div
id="glitch-rgb" id="caption-glitch-rgb"
data-composition-id="caption-glitch-rgb" data-composition-id="caption-glitch-rgb"
data-start="0" data-start="0"
data-duration="8" data-duration="8"
@@ -121,30 +128,7 @@
(function () { (function () {
window.__timelines = window.__timelines || {}; window.__timelines = window.__timelines || {};
var _fitCanvas = document.createElement("canvas"); var TRANSCRIPT = [
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: "Every", start: 0.0, end: 0.3 },
{ text: "great", start: 0.3, end: 0.55 }, { text: "great", start: 0.3, end: 0.55 },
{ text: "video", start: 0.55, end: 0.85 }, { text: "video", start: 0.55, end: 0.85 },
@@ -175,27 +159,69 @@
{ text: "cinema.", start: 7.3, end: 7.7 }, { text: "cinema.", start: 7.3, end: 7.7 },
]; ];
// Keywords get white + glow on activation var _fitCanvas = document.createElement("canvas");
var KEYWORDS = new Set([8, 12, 15, 24, 27]); // HyperFrames, HTML, professional, code, cinema. 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 RAW_GROUPS = [ function mulberry32(seed) {
[0, 3], return function () {
[4, 7], seed |= 0;
[8, 8], seed = (seed + 0x6d2b79f5) | 0;
[9, 12], var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
[13, 15], t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
[16, 16], return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
[17, 19], };
[20, 22], }
[23, 24],
[25, 27], 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 GROUPS = RAW_GROUPS.map(function (pair, gi) {
var ws = pair[0], var ws = pair[0];
we = pair[1]; var we = pair[1];
var nextStart = gi + 1 < RAW_GROUPS.length ? WORDS[RAW_GROUPS[gi + 1][0]].start : 8.0; var nextStart =
var gEnd = Math.min(WORDS[we].end + 0.35, nextStart - 0.05); gi + 1 < RAW_GROUPS.length ? TRANSCRIPT[RAW_GROUPS[gi + 1][0]].start : 8.0;
return { wordStart: ws, wordEnd: we, start: WORDS[ws].start, end: gEnd }; 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 BASE_FONT = 88;
@@ -203,7 +229,7 @@
var tl = gsap.timeline({ paused: true }); var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) { GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1); var groupWords = TRANSCRIPT.slice(g.wordStart, g.wordEnd + 1);
var grp = document.createElement("div"); var grp = document.createElement("div");
grp.className = "gl-group"; grp.className = "gl-group";
grp.id = "gl-grp-" + gi; grp.id = "gl-grp-" + gi;
@@ -230,77 +256,83 @@
tl.set(grp, { visibility: "visible" }, g.start); tl.set(grp, { visibility: "visible" }, g.start);
tl.fromTo( tl.fromTo(
grp, grp,
{ opacity: 0, x: -8 }, { opacity: 0 },
{ opacity: 1, x: 0, duration: 0.18, ease: "power3.out" }, { opacity: 1, duration: 0.12, ease: "power2.out" },
g.start, g.start,
); );
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var wordEl = document.getElementById("gl-w-" + wi); var wordEl = document.getElementById("gl-w-" + wi);
var isKW = KEYWORDS.has(wi); var plan = glitchPlan(w, TRANSCRIPT[wi + 1]);
var rng = mulberry32(wi * 777 + 13); var rng = mulberry32(wi * 777 + 13);
var j1 = (rng() - 0.5) * 10; var travel = -(8 + rng() * 8);
var j2 = (rng() - 0.5) * 8; var shadowMag = 5 + rng() * 7;
var j3 = (rng() - 0.5) * 6;
var shadowMag = 3 + rng() * 4;
var sm = shadowMag.toFixed(1); 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( tl.to(
wordEl, wordEl,
{ {
x: j1, x: travel,
color: "#ffffff", textShadow:
textShadow: sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff", sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff, 0 5px 18px rgba(0,0,0,0.52)",
duration: 0.045, duration: plan.d1,
ease: "none", ease: "none",
}, },
w.start, w.start,
); );
tl.to(
wordEl,
{
x: j2,
textShadow:
"-" +
(shadowMag * 0.7).toFixed(1) +
"px 0 #ff003c, " +
(shadowMag * 0.7).toFixed(1) +
"px 0 #00e5ff",
duration: 0.038,
ease: "none",
},
w.start + 0.045,
);
tl.to(
wordEl,
{
x: j3,
textShadow: sm + "px 0 #ff003c, -" + sm + "px 0 #00e5ff",
duration: 0.03,
ease: "none",
},
w.start + 0.083,
);
tl.to( tl.to(
wordEl, wordEl,
{ {
x: 0, x: 0,
textShadow: "none", textShadow: "0 5px 18px rgba(0,0,0,0.52)",
color: isKW ? "#ffffff" : "#00e5ff", duration: plan.d2,
duration: 0.05, ease: "power3.out",
ease: "power2.out",
}, },
w.start + 0.113, w.start + plan.d1,
);
tl.to(
wordEl,
{ color: "rgba(180,230,255,0.4)", textShadow: "none", duration: 0.12 },
w.end,
); );
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, x: 8, duration: 0.15 }, g.end - 0.15); 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.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
@@ -12,5 +12,5 @@
"type": "hyperframes:snippet" "type": "hyperframes:snippet"
} }
], ],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-glitch-rgb/preview.mp4?v=1779051416" "preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-glitch-rgb/preview.mp4?v=1779065047"
} }
@@ -233,11 +233,11 @@
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var el = document.getElementById("gr-w-" + wi); var el = document.getElementById("gr-w-" + wi);
tl.to(el, { opacity: 1, scale: 1.1, duration: 0.08 }, w.start); tl.to(el, { opacity: 1, scale: 1.1, duration: 0.15 }, w.start);
tl.to(el, { opacity: 0.3, scale: 1, duration: 0.08 }, w.end); tl.to(el, { opacity: 0.3, scale: 1, duration: 0.15 }, w.end);
}); });
tl.to(grp, { y: -26, opacity: 0, duration: 0.18 }, g.end - 0.18); tl.to(grp, { y: -26, opacity: 0, duration: 0.25 }, g.end - 0.25);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end); tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
+3 -3
View File
@@ -233,11 +233,11 @@
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var el = document.getElementById("gr-w-" + wi); var el = document.getElementById("gr-w-" + wi);
tl.to(el, { opacity: 1, scale: 1.1, duration: 0.08 }, w.start); tl.to(el, { opacity: 1, scale: 1.1, duration: 0.15 }, w.start);
tl.to(el, { opacity: 0.3, scale: 1, duration: 0.08 }, w.end); tl.to(el, { opacity: 0.3, scale: 1, duration: 0.15 }, w.end);
}); });
tl.to(grp, { y: -26, opacity: 0, duration: 0.18 }, g.end - 0.18); tl.to(grp, { y: -26, opacity: 0, duration: 0.25 }, g.end - 0.25);
tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end); tl.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
@@ -12,5 +12,5 @@
"type": "hyperframes:snippet" "type": "hyperframes:snippet"
} }
], ],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-gradient-fill/preview.mp4?v=1779051416" "preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-gradient-fill/preview.mp4?v=1779057707"
} }
@@ -3,11 +3,11 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" /> <meta name="viewport" content="width=1920, height=1080" />
<title>Typewriter</title> <title>Highlight</title>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@800&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
@@ -23,16 +23,16 @@
height: 1080px; height: 1080px;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
background: #000; background: #0a0a0a;
} }
#typewriter { #highlight {
position: relative; position: relative;
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
overflow: hidden; overflow: hidden;
background: #000; background: #0a0a0a;
} }
#tw-video { #hl-video {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 0; z-index: 0;
@@ -40,78 +40,77 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.tw-overlay { .hl-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.4);
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
#tw-container { #hl-container {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 10; z-index: 10;
pointer-events: none; pointer-events: none;
} }
.tw-group { .hl-group {
position: absolute; position: absolute;
bottom: 120px; bottom: 140px;
left: 0; left: 0;
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
gap: 24px; gap: 8px;
padding: 0 80px; padding: 0 100px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
} }
.tw-word { .hl-word {
font-family: "JetBrains Mono", monospace; font-family: "Montserrat", sans-serif;
font-weight: 700; font-weight: 800;
font-size: 76px; font-size: 80px;
color: #00ff41; text-transform: uppercase;
text-shadow: 0 0 12px rgba(0, 255, 65, 0.5); color: #ffffff;
display: inline-block; display: inline-block;
letter-spacing: 0.04em; letter-spacing: 0.02em;
line-height: 1; line-height: 1;
white-space: nowrap; 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;
} }
.tw-char { .hl-word-bg {
display: none; 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;
} }
.tw-cursor { .hl-word-text {
font-family: "JetBrains Mono", monospace; position: relative;
font-weight: 700; z-index: 1;
font-size: 76px;
color: #00ff41;
text-shadow: 0 0 12px rgba(0, 255, 65, 0.5);
display: none;
} }
</style> </style>
</head> </head>
<body> <body>
<div <div
id="typewriter" id="highlight"
data-composition-id="caption-typewriter" data-composition-id="caption-highlight"
data-start="0" data-start="0"
data-duration="8" data-duration="8"
data-fps="30" data-fps="30"
data-width="1920" data-width="1920"
data-height="1080" data-height="1080"
> >
<video <div class="hl-overlay"></div>
id="tw-video" <div id="hl-container"></div>
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="tw-overlay"></div>
<div id="tw-container"></div>
</div> </div>
<script> <script>
@@ -162,82 +161,98 @@
{ text: "cinema.", start: 7.3, end: 7.7 }, { text: "cinema.", start: 7.3, end: 7.7 },
]; ];
var GROUPS = [ var RAW_GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.1 }, [0, 3],
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 }, [4, 7],
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 }, [8, 8],
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 }, [9, 12],
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 }, [13, 15],
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 }, [16, 16],
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 }, [17, 19],
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 }, [20, 22],
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 }, [23, 24],
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 }, [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("tw-container"); var container = document.getElementById("hl-container");
var tl = gsap.timeline({ paused: true }); var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) { GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1); 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 var groupText = groupWords
.map(function (w) { .map(function (w) {
return w.text; return w.text.toUpperCase();
}) })
.join(" "); .join(" ");
var fontSize = fitFontSize(groupText, 76, "700", "'JetBrains Mono', monospace", 1760); var computedSize = fitFontSize(groupText, 80, "800", "Montserrat", 1620);
var grp = document.createElement("div");
grp.className = "tw-group";
grp.id = "tw-grp-" + gi;
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var wordWrap = document.createElement("span"); var wordEl = document.createElement("span");
wordWrap.className = "tw-word"; wordEl.className = "hl-word";
wordWrap.id = "tw-w-" + wi; wordEl.id = "hl-w-" + wi;
wordWrap.style.fontSize = fontSize + "px"; wordEl.style.fontSize = computedSize + "px";
w.text.split("").forEach(function (c, ci) { var bgEl = document.createElement("span");
var charEl = document.createElement("span"); bgEl.className = "hl-word-bg";
charEl.className = "tw-char"; bgEl.id = "hl-bg-" + wi;
charEl.id = "tw-c-" + wi + "-" + ci;
charEl.textContent = c;
wordWrap.appendChild(charEl);
});
var cursor = document.createElement("span"); var textEl = document.createElement("span");
cursor.className = "tw-cursor"; textEl.className = "hl-word-text";
cursor.id = "tw-cur-" + wi; textEl.textContent = w.text.toUpperCase();
cursor.textContent = "_";
cursor.style.fontSize = fontSize + "px"; wordEl.appendChild(bgEl);
wordWrap.appendChild(cursor); wordEl.appendChild(textEl);
grp.appendChild(wordWrap); grp.appendChild(wordEl);
}); });
container.appendChild(grp); container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 1 }, g.start); 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) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var chars = w.text.split(""); var bgEl = document.getElementById("hl-bg-" + wi);
var wordDuration = Math.max(w.end - w.start, 0.1); var wordEl = document.getElementById("hl-w-" + wi);
var charCount = chars.length;
var cursorEl = document.getElementById("tw-cur-" + wi);
tl.set(cursorEl, { display: "inline" }, w.start); tl.to(bgEl, { opacity: 1, scaleX: 1, duration: 0.15, ease: "power2.out" }, w.start);
chars.forEach(function (c, ci) { tl.to(
var charEl = document.getElementById("tw-c-" + wi + "-" + ci); wordEl,
tl.set(charEl, { display: "inline" }, w.start + ci * (wordDuration / charCount)); { filter: "brightness(1.05)", duration: 0.08, ease: "power2.out" },
}); w.start,
tl.set(cursorEl, { display: "none" }, w.end); );
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.2 }, g.end - 0.2); 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.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
window.__timelines["caption-typewriter"] = tl; tl.seek(0);
window.__timelines["caption-highlight"] = tl;
})(); })();
</script> </script>
</body> </body>
@@ -3,11 +3,11 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" /> <meta name="viewport" content="width=1920, height=1080" />
<title>Typewriter</title> <title>Highlight</title>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@700&display=swap" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@800&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
@@ -23,16 +23,16 @@
height: 1080px; height: 1080px;
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
background: #000; background: #0a0a0a;
} }
#typewriter { #highlight {
position: relative; position: relative;
width: 1920px; width: 1920px;
height: 1080px; height: 1080px;
overflow: hidden; overflow: hidden;
background: #000; background: #0a0a0a;
} }
#tw-video { #hl-video {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 0; z-index: 0;
@@ -40,78 +40,77 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.tw-overlay { .hl-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(0, 0, 0, 0.85); background: rgba(0, 0, 0, 0.4);
z-index: 1; z-index: 1;
pointer-events: none; pointer-events: none;
} }
#tw-container { #hl-container {
position: absolute; position: absolute;
inset: 0; inset: 0;
z-index: 10; z-index: 10;
pointer-events: none; pointer-events: none;
} }
.tw-group { .hl-group {
position: absolute; position: absolute;
bottom: 120px; bottom: 140px;
left: 0; left: 0;
width: 100%; width: 100%;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
gap: 24px; gap: 8px;
padding: 0 80px; padding: 0 100px;
opacity: 0; opacity: 0;
visibility: hidden; visibility: hidden;
} }
.tw-word { .hl-word {
font-family: "JetBrains Mono", monospace; font-family: "Montserrat", sans-serif;
font-weight: 700; font-weight: 800;
font-size: 76px; font-size: 80px;
color: #00ff41; text-transform: uppercase;
text-shadow: 0 0 12px rgba(0, 255, 65, 0.5); color: #ffffff;
display: inline-block; display: inline-block;
letter-spacing: 0.04em; letter-spacing: 0.02em;
line-height: 1; line-height: 1;
white-space: nowrap; 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;
} }
.tw-char { .hl-word-bg {
display: none; 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;
} }
.tw-cursor { .hl-word-text {
font-family: "JetBrains Mono", monospace; position: relative;
font-weight: 700; z-index: 1;
font-size: 76px;
color: #00ff41;
text-shadow: 0 0 12px rgba(0, 255, 65, 0.5);
display: none;
} }
</style> </style>
</head> </head>
<body> <body>
<div <div
id="typewriter" id="highlight"
data-composition-id="caption-typewriter" data-composition-id="caption-highlight"
data-start="0" data-start="0"
data-duration="8" data-duration="8"
data-fps="30" data-fps="30"
data-width="1920" data-width="1920"
data-height="1080" data-height="1080"
> >
<video <div class="hl-overlay"></div>
id="tw-video" <div id="hl-container"></div>
muted
playsinline
data-start="0"
data-duration="8"
data-track-index="0"
data-has-audio="false"
></video>
<div class="tw-overlay"></div>
<div id="tw-container"></div>
</div> </div>
<script> <script>
@@ -162,82 +161,98 @@
{ text: "cinema.", start: 7.3, end: 7.7 }, { text: "cinema.", start: 7.3, end: 7.7 },
]; ];
var GROUPS = [ var RAW_GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.1 }, [0, 3],
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 }, [4, 7],
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 }, [8, 8],
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 }, [9, 12],
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 }, [13, 15],
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 }, [16, 16],
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 }, [17, 19],
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 }, [20, 22],
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 }, [23, 24],
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 }, [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("tw-container"); var container = document.getElementById("hl-container");
var tl = gsap.timeline({ paused: true }); var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (g, gi) { GROUPS.forEach(function (g, gi) {
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1); 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 var groupText = groupWords
.map(function (w) { .map(function (w) {
return w.text; return w.text.toUpperCase();
}) })
.join(" "); .join(" ");
var fontSize = fitFontSize(groupText, 76, "700", "'JetBrains Mono', monospace", 1760); var computedSize = fitFontSize(groupText, 80, "800", "Montserrat", 1620);
var grp = document.createElement("div");
grp.className = "tw-group";
grp.id = "tw-grp-" + gi;
groupWords.forEach(function (w, i) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var wordWrap = document.createElement("span"); var wordEl = document.createElement("span");
wordWrap.className = "tw-word"; wordEl.className = "hl-word";
wordWrap.id = "tw-w-" + wi; wordEl.id = "hl-w-" + wi;
wordWrap.style.fontSize = fontSize + "px"; wordEl.style.fontSize = computedSize + "px";
w.text.split("").forEach(function (c, ci) { var bgEl = document.createElement("span");
var charEl = document.createElement("span"); bgEl.className = "hl-word-bg";
charEl.className = "tw-char"; bgEl.id = "hl-bg-" + wi;
charEl.id = "tw-c-" + wi + "-" + ci;
charEl.textContent = c;
wordWrap.appendChild(charEl);
});
var cursor = document.createElement("span"); var textEl = document.createElement("span");
cursor.className = "tw-cursor"; textEl.className = "hl-word-text";
cursor.id = "tw-cur-" + wi; textEl.textContent = w.text.toUpperCase();
cursor.textContent = "_";
cursor.style.fontSize = fontSize + "px"; wordEl.appendChild(bgEl);
wordWrap.appendChild(cursor); wordEl.appendChild(textEl);
grp.appendChild(wordWrap); grp.appendChild(wordEl);
}); });
container.appendChild(grp); container.appendChild(grp);
tl.set(grp, { visibility: "visible", opacity: 1 }, g.start); 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) { groupWords.forEach(function (w, i) {
var wi = g.wordStart + i; var wi = g.wordStart + i;
var chars = w.text.split(""); var bgEl = document.getElementById("hl-bg-" + wi);
var wordDuration = Math.max(w.end - w.start, 0.1); var wordEl = document.getElementById("hl-w-" + wi);
var charCount = chars.length;
var cursorEl = document.getElementById("tw-cur-" + wi);
tl.set(cursorEl, { display: "inline" }, w.start); tl.to(bgEl, { opacity: 1, scaleX: 1, duration: 0.15, ease: "power2.out" }, w.start);
chars.forEach(function (c, ci) { tl.to(
var charEl = document.getElementById("tw-c-" + wi + "-" + ci); wordEl,
tl.set(charEl, { display: "inline" }, w.start + ci * (wordDuration / charCount)); { filter: "brightness(1.05)", duration: 0.08, ease: "power2.out" },
}); w.start,
tl.set(cursorEl, { display: "none" }, w.end); );
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.2 }, g.end - 0.2); 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.set(grp, { opacity: 0, visibility: "hidden" }, g.end);
}); });
window.__timelines["caption-typewriter"] = tl; tl.seek(0);
window.__timelines["caption-highlight"] = tl;
})(); })();
</script> </script>
</body> </body>
@@ -0,0 +1,16 @@
{
"$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": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-highlight/preview.mp4"
}
@@ -181,16 +181,16 @@
]; ];
var GROUPS = [ var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 }, { wordStart: 0, wordEnd: 3, start: 0.0, end: 1.35 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 }, { wordStart: 4, wordEnd: 7, start: 1.1, end: 2.35 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 }, { wordStart: 8, wordEnd: 8, start: 2.1, end: 2.9 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 }, { wordStart: 9, wordEnd: 12, start: 2.65, end: 3.8 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 }, { wordStart: 13, wordEnd: 15, start: 3.55, end: 4.7 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 }, { wordStart: 16, wordEnd: 16, start: 3.95, end: 5.0 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 }, { wordStart: 17, wordEnd: 19, start: 4.9, end: 5.7 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 }, { wordStart: 20, wordEnd: 22, start: 5.65, end: 6.5 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 }, { wordStart: 23, wordEnd: 24, start: 6.35, end: 7.05 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 }, { wordStart: 25, wordEnd: 27, start: 6.8, end: 7.9 },
]; ];
var container = document.getElementById("sc-container"); var container = document.getElementById("sc-container");
@@ -248,10 +248,10 @@
var scr1El = document.getElementById("sc-scr1-" + wi); var scr1El = document.getElementById("sc-scr1-" + wi);
tl.set(scr0El, { display: "inline" }, w.start); tl.set(scr0El, { display: "inline" }, w.start);
tl.set(scr1El, { display: "inline" }, w.start + 0.05); tl.set(scr1El, { display: "inline" }, w.start + 0.1);
tl.set(scr0El, { display: "none" }, w.start + 0.05); tl.set(scr0El, { display: "none" }, w.start + 0.1);
tl.set(realEl, { display: "inline" }, w.start + 0.1); tl.set(realEl, { display: "inline" }, w.start + 0.2);
tl.set(scr1El, { display: "none" }, w.start + 0.1); tl.set(scr1El, { display: "none" }, w.start + 0.2);
}); });
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12); tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
+14 -14
View File
@@ -181,16 +181,16 @@
]; ];
var GROUPS = [ var GROUPS = [
{ wordStart: 0, wordEnd: 3, start: 0.0, end: 1.05 }, { wordStart: 0, wordEnd: 3, start: 0.0, end: 1.35 },
{ wordStart: 4, wordEnd: 7, start: 1.1, end: 2.05 }, { wordStart: 4, wordEnd: 7, start: 1.1, end: 2.35 },
{ wordStart: 8, wordEnd: 8, start: 2.1, end: 2.6 }, { wordStart: 8, wordEnd: 8, start: 2.1, end: 2.9 },
{ wordStart: 9, wordEnd: 12, start: 2.65, end: 3.5 }, { wordStart: 9, wordEnd: 12, start: 2.65, end: 3.8 },
{ wordStart: 13, wordEnd: 15, start: 3.55, end: 4.4 }, { wordStart: 13, wordEnd: 15, start: 3.55, end: 4.7 },
{ wordStart: 16, wordEnd: 16, start: 3.95, end: 4.75 }, { wordStart: 16, wordEnd: 16, start: 3.95, end: 5.0 },
{ wordStart: 17, wordEnd: 19, start: 4.9, end: 5.4 }, { wordStart: 17, wordEnd: 19, start: 4.9, end: 5.7 },
{ wordStart: 20, wordEnd: 22, start: 5.65, end: 6.2 }, { wordStart: 20, wordEnd: 22, start: 5.65, end: 6.5 },
{ wordStart: 23, wordEnd: 24, start: 6.35, end: 6.75 }, { wordStart: 23, wordEnd: 24, start: 6.35, end: 7.05 },
{ wordStart: 25, wordEnd: 27, start: 6.8, end: 7.8 }, { wordStart: 25, wordEnd: 27, start: 6.8, end: 7.9 },
]; ];
var container = document.getElementById("sc-container"); var container = document.getElementById("sc-container");
@@ -248,10 +248,10 @@
var scr1El = document.getElementById("sc-scr1-" + wi); var scr1El = document.getElementById("sc-scr1-" + wi);
tl.set(scr0El, { display: "inline" }, w.start); tl.set(scr0El, { display: "inline" }, w.start);
tl.set(scr1El, { display: "inline" }, w.start + 0.05); tl.set(scr1El, { display: "inline" }, w.start + 0.1);
tl.set(scr0El, { display: "none" }, w.start + 0.05); tl.set(scr0El, { display: "none" }, w.start + 0.1);
tl.set(realEl, { display: "inline" }, w.start + 0.1); tl.set(realEl, { display: "inline" }, w.start + 0.2);
tl.set(scr1El, { display: "none" }, w.start + 0.1); tl.set(scr1El, { display: "none" }, w.start + 0.2);
}); });
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12); tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
@@ -12,5 +12,5 @@
"type": "hyperframes:snippet" "type": "hyperframes:snippet"
} }
], ],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-matrix-decode/preview.mp4?v=1779051416" "preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-matrix-decode/preview.mp4?v=1779060427"
} }
@@ -1,16 +0,0 @@
{
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
"name": "caption-typewriter",
"type": "hyperframes:component",
"title": "Typewriter",
"description": "Green terminal character-by-character reveal with blinking cursor",
"tags": ["captions", "caption-style", "terminal", "typewriter", "hacker"],
"files": [
{
"path": "caption-typewriter.html",
"target": "compositions/components/caption-typewriter.html",
"type": "hyperframes:snippet"
}
],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-typewriter/preview.mp4?v=1779051416"
}
@@ -59,7 +59,7 @@
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 1400px; width: 1400px;
height: 260px; height: 278px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -70,7 +70,7 @@
left: 0; left: 0;
top: 0; top: 0;
width: 1400px; width: 1400px;
height: 260px; height: 278px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -224,7 +224,7 @@
} }
function splitLines(words) { function splitLines(words) {
if (measureLineWidth(words) <= MAX_LINE_WIDTH) { if (words.length < 2) {
return [{ words: words, startIndex: 0 }]; return [{ words: words, startIndex: 0 }];
} }
var bestSplit = Math.ceil(words.length / 2); var bestSplit = Math.ceil(words.length / 2);
@@ -248,6 +248,35 @@
return result; 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) { function makeGroups(words) {
var groups = []; var groups = [];
var current = []; var current = [];
@@ -273,7 +302,7 @@
} }
}); });
if (current.length) groups.push(makeGroup(current)); if (current.length) groups.push(makeGroup(current));
return groups; return avoidSingleWordGroups(groups);
} }
function makeGroup(words) { function makeGroup(words) {
@@ -311,9 +340,10 @@
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex; lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT; lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px"; lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word) { line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span"); var wordEl = document.createElement("span");
wordEl.textContent = word.text.toLowerCase(); wordEl.textContent = word.text.toLowerCase();
wordEl.style.display = "inline-block";
lineEl.appendChild(wordEl); lineEl.appendChild(wordEl);
}); });
copyEl.appendChild(lineEl); copyEl.appendChild(lineEl);
@@ -362,6 +392,7 @@
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1"); var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0]; var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start; var switchTime = line2FirstWord.start;
tl.to( tl.to(
line1El, line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" }, { fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
+36 -5
View File
@@ -59,7 +59,7 @@
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
width: 1400px; width: 1400px;
height: 260px; height: 278px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -70,7 +70,7 @@
left: 0; left: 0;
top: 0; top: 0;
width: 1400px; width: 1400px;
height: 260px; height: 278px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -224,7 +224,7 @@
} }
function splitLines(words) { function splitLines(words) {
if (measureLineWidth(words) <= MAX_LINE_WIDTH) { if (words.length < 2) {
return [{ words: words, startIndex: 0 }]; return [{ words: words, startIndex: 0 }];
} }
var bestSplit = Math.ceil(words.length / 2); var bestSplit = Math.ceil(words.length / 2);
@@ -248,6 +248,35 @@
return result; 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) { function makeGroups(words) {
var groups = []; var groups = [];
var current = []; var current = [];
@@ -273,7 +302,7 @@
} }
}); });
if (current.length) groups.push(makeGroup(current)); if (current.length) groups.push(makeGroup(current));
return groups; return avoidSingleWordGroups(groups);
} }
function makeGroup(words) { function makeGroup(words) {
@@ -311,9 +340,10 @@
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex; lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT; lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px"; lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word) { line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span"); var wordEl = document.createElement("span");
wordEl.textContent = word.text.toLowerCase(); wordEl.textContent = word.text.toLowerCase();
wordEl.style.display = "inline-block";
lineEl.appendChild(wordEl); lineEl.appendChild(wordEl);
}); });
copyEl.appendChild(lineEl); copyEl.appendChild(lineEl);
@@ -362,6 +392,7 @@
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1"); var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0]; var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start; var switchTime = line2FirstWord.start;
tl.to( tl.to(
line1El, line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" }, { fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
@@ -12,5 +12,5 @@
"type": "hyperframes:snippet" "type": "hyperframes:snippet"
} }
], ],
"preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-weight-shift/preview.mp4?v=1779051416" "preview": "https://static.heygen.ai/hyperframes-oss/registry/components/caption-weight-shift/preview.mp4?v=1779065047"
} }
+4 -4
View File
@@ -95,10 +95,6 @@
"name": "caption-glitch-rgb", "name": "caption-glitch-rgb",
"type": "hyperframes:component" "type": "hyperframes:component"
}, },
{
"name": "caption-typewriter",
"type": "hyperframes:component"
},
{ {
"name": "caption-matrix-decode", "name": "caption-matrix-decode",
"type": "hyperframes:component" "type": "hyperframes:component"
@@ -127,6 +123,10 @@
"name": "caption-neon-glow", "name": "caption-neon-glow",
"type": "hyperframes:component" "type": "hyperframes:component"
}, },
{
"name": "caption-highlight",
"type": "hyperframes:component"
},
{ {
"name": "instagram-follow", "name": "instagram-follow",
"type": "hyperframes:block" "type": "hyperframes:block"