mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-14 13:27:13 +00:00
feat(registry): add 15 caption style catalog components
Add 15 caption overlay components to the registry, covering a wide range of animation techniques: Standalone origins (6): - caption-pill-karaoke: pill container + karaoke word highlight - caption-neon-accent: multi-color neon glow with wiggle drift - caption-weight-shift: font-weight transition between lines - caption-emoji-pop: emoji + stroked text + horizontal squeeze - caption-editorial-emphasis: dual-font (Inter + Playfair) emphasis - caption-parallax-layers: 3D text layering with vertical stretch Gallery picks (9): - caption-glitch-rgb: RGB chromatic aberration + CRT scanlines - caption-typewriter: green terminal char-by-char with cursor - caption-matrix-decode: scramble-reveal character animation - caption-particle-burst: keyword particle explosions - caption-texture-lava: flowing lava texture mask - caption-clip-wipe: clip-path left-to-right wipe reveal - caption-kinetic-slam: full-screen single-word slam - caption-gradient-fill: gradient-clipped text + elastic bounce - caption-neon-glow: cyan/magenta neon glow accents Each component includes the reusable HTML, a demo composition, and registry-item.json. All use a generic HyperFrames transcript. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
7a1e8769a5
commit
78ddd2602b
@@ -0,0 +1,170 @@
|
||||
<!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 || {};
|
||||
|
||||
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 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 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;
|
||||
|
||||
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 = '88px';
|
||||
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.15, ease: 'power3.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.12 }, w.end);
|
||||
});
|
||||
|
||||
var wordEls = grp.querySelectorAll('.wp-word');
|
||||
tl.to(wordEls, {
|
||||
clipPath: 'inset(0 0% 0 100%)',
|
||||
duration: 0.14, stagger: 0.04, ease: 'power2.in'
|
||||
}, g.end - 0.2);
|
||||
tl.set(grp, { visibility: 'hidden', opacity: 0 }, g.end);
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines['clip-wipe'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
<!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 || {};
|
||||
|
||||
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 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 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;
|
||||
|
||||
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 = '88px';
|
||||
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.15, ease: 'power3.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.12 }, w.end);
|
||||
});
|
||||
|
||||
var wordEls = grp.querySelectorAll('.wp-word');
|
||||
tl.to(wordEls, {
|
||||
clipPath: 'inset(0 0% 0 100%)',
|
||||
duration: 0.14, stagger: 0.04, ease: 'power2.in'
|
||||
}, g.end - 0.2);
|
||||
tl.set(grp, { visibility: 'hidden', opacity: 0 }, g.end);
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines['clip-wipe'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
<!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;
|
||||
|
||||
// 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 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 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;
|
||||
l1.appendChild(span);
|
||||
});
|
||||
el.appendChild(l1);
|
||||
|
||||
if (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;
|
||||
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["editorial-emphasis"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,303 @@
|
||||
<!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;
|
||||
|
||||
// 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 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 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;
|
||||
l1.appendChild(span);
|
||||
});
|
||||
el.appendChild(l1);
|
||||
|
||||
if (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;
|
||||
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["editorial-emphasis"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<!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 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 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.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["emoji-pop"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+367
@@ -0,0 +1,367 @@
|
||||
<!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 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 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.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["emoji-pop"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<!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;
|
||||
}
|
||||
#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;
|
||||
background: rgba(0,0,0,0.45); z-index: 1; 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.08) 3px, rgba(0,0,0,0.08) 4px
|
||||
);
|
||||
}
|
||||
#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: 24px; 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: rgba(180,230,255,0.45);
|
||||
display: inline-block; letter-spacing: 0.05em; line-height: 1;
|
||||
will-change: transform, text-shadow, color;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="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 || {};
|
||||
|
||||
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 white + glow on activation
|
||||
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 BASE_FONT = 88;
|
||||
var container = document.getElementById('gl-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 = 'gl-group';
|
||||
grp.id = 'gl-grp-' + gi;
|
||||
|
||||
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 = BASE_FONT + 'px';
|
||||
grp.appendChild(span);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
tl.set(grp, { visibility: 'visible' }, g.start);
|
||||
tl.fromTo(grp,
|
||||
{ opacity: 0, x: -8 },
|
||||
{ opacity: 1, x: 0, duration: 0.18, ease: 'power3.out' },
|
||||
g.start
|
||||
);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var wordEl = document.getElementById('gl-w-' + wi);
|
||||
var isKW = KEYWORDS.has(wi);
|
||||
var rng = mulberry32(wi * 777 + 13);
|
||||
var j1 = (rng() - 0.5) * 10;
|
||||
var j2 = (rng() - 0.5) * 8;
|
||||
var j3 = (rng() - 0.5) * 6;
|
||||
var shadowMag = 3 + rng() * 4;
|
||||
var sm = shadowMag.toFixed(1);
|
||||
|
||||
tl.to(wordEl, { x: j1, color: '#ffffff', textShadow: sm + 'px 0 #ff003c, -' + sm + 'px 0 #00e5ff', duration: 0.045, ease: 'none' }, 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(wordEl, { x: 0, textShadow: 'none', color: isKW ? '#ffffff' : '#00e5ff', duration: 0.05, ease: 'power2.out' }, w.start + 0.113);
|
||||
tl.to(wordEl, { color: 'rgba(180,230,255,0.4)', textShadow: 'none', duration: 0.12 }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, x: 8, duration: 0.15 }, g.end - 0.15);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines['glitch-rgb'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
<!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;
|
||||
}
|
||||
#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;
|
||||
background: rgba(0,0,0,0.45); z-index: 1; 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.08) 3px, rgba(0,0,0,0.08) 4px
|
||||
);
|
||||
}
|
||||
#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: 24px; 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: rgba(180,230,255,0.45);
|
||||
display: inline-block; letter-spacing: 0.05em; line-height: 1;
|
||||
will-change: transform, text-shadow, color;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="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 || {};
|
||||
|
||||
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 white + glow on activation
|
||||
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 BASE_FONT = 88;
|
||||
var container = document.getElementById('gl-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 = 'gl-group';
|
||||
grp.id = 'gl-grp-' + gi;
|
||||
|
||||
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 = BASE_FONT + 'px';
|
||||
grp.appendChild(span);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
tl.set(grp, { visibility: 'visible' }, g.start);
|
||||
tl.fromTo(grp,
|
||||
{ opacity: 0, x: -8 },
|
||||
{ opacity: 1, x: 0, duration: 0.18, ease: 'power3.out' },
|
||||
g.start
|
||||
);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var wordEl = document.getElementById('gl-w-' + wi);
|
||||
var isKW = KEYWORDS.has(wi);
|
||||
var rng = mulberry32(wi * 777 + 13);
|
||||
var j1 = (rng() - 0.5) * 10;
|
||||
var j2 = (rng() - 0.5) * 8;
|
||||
var j3 = (rng() - 0.5) * 6;
|
||||
var shadowMag = 3 + rng() * 4;
|
||||
var sm = shadowMag.toFixed(1);
|
||||
|
||||
tl.to(wordEl, { x: j1, color: '#ffffff', textShadow: sm + 'px 0 #ff003c, -' + sm + 'px 0 #00e5ff', duration: 0.045, ease: 'none' }, 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(wordEl, { x: 0, textShadow: 'none', color: isKW ? '#ffffff' : '#00e5ff', duration: 0.05, ease: 'power2.out' }, w.start + 0.113);
|
||||
tl.to(wordEl, { color: 'rgba(180,230,255,0.4)', textShadow: 'none', duration: 0.12 }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, x: 8, duration: 0.15 }, g.end - 0.15);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
tl.seek(0);
|
||||
window.__timelines['glitch-rgb'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<!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=Nunito: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-video {
|
||||
position: absolute; inset: 0; z-index: 0;
|
||||
width: 100%; height: 100%; object-fit: cover;
|
||||
}
|
||||
.gr-overlay {
|
||||
position: absolute; inset: 0;
|
||||
background: rgba(0,0,0,0.42); z-index: 1; pointer-events: none;
|
||||
}
|
||||
#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: 'Nunito', sans-serif;
|
||||
font-weight: 900; font-size: 100px;
|
||||
display: inline-block; letter-spacing: 0.04em; line-height: 1;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
opacity: 0.3;
|
||||
}
|
||||
</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 class="gr-overlay"></div>
|
||||
<div id="gr-container"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
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},
|
||||
];
|
||||
|
||||
// Enhanced gradient palette with accent for keywords
|
||||
var GRADS = [
|
||||
"linear-gradient(135deg,#FF6B6B,#FFD700)",
|
||||
"linear-gradient(135deg,#4ADE80,#22D3EE)",
|
||||
"linear-gradient(135deg,#818CF8,#E879F9)",
|
||||
"linear-gradient(135deg,#FFD700,#FF6B35)",
|
||||
"linear-gradient(135deg,#22D3EE,#4ADE80)",
|
||||
"linear-gradient(135deg,#E879F9,#818CF8)",
|
||||
"linear-gradient(135deg,#FF3366,#FF6B35)"
|
||||
];
|
||||
var KEYWORD_GRADS = {
|
||||
8: "linear-gradient(135deg,#FF6B6B,#FFD700,#FF6B6B)", // HyperFrames
|
||||
12: "linear-gradient(135deg,#22D3EE,#ffffff,#22D3EE)", // HTML
|
||||
15: "linear-gradient(135deg,#818CF8,#E879F9,#818CF8)", // professional
|
||||
24: "linear-gradient(135deg,#00F5D4,#22D3EE,#00F5D4)", // code
|
||||
27: "linear-gradient(135deg,#FFD700,#FF6B35,#FFD700)" // cinema.
|
||||
};
|
||||
|
||||
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 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 = '100px';
|
||||
var grad = KEYWORD_GRADS[wi] || GRADS[wi % 7];
|
||||
span.style.background = grad;
|
||||
span.style.webkitBackgroundClip = 'text';
|
||||
span.style.backgroundClip = 'text';
|
||||
span.style.color = 'transparent';
|
||||
grp.appendChild(span);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
var gs = g.start;
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var el = document.getElementById('gr-w-' + wi);
|
||||
tl.set(el, { opacity: 0, y: 55, scale: 0.35 }, gs);
|
||||
tl.fromTo(el,
|
||||
{ y: 55, scale: 0.35, opacity: 0 },
|
||||
{ y: 0, scale: 1, opacity: 0.35, duration: 0.55, ease: 'elastic.out(1.6,0.4)' },
|
||||
gs + i * 0.05
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(grp, { visibility: 'visible', opacity: 1 }, gs);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var el = document.getElementById('gr-w-' + wi);
|
||||
tl.to(el, { opacity: 1, scale: 1.1, duration: 0.08 }, w.start);
|
||||
tl.to(el, { opacity: 0.3, scale: 1, duration: 0.08 }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { y: -26, opacity: 0, duration: 0.18 }, g.end - 0.18);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['gradient-fill'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
<!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=Nunito: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-video {
|
||||
position: absolute; inset: 0; z-index: 0;
|
||||
width: 100%; height: 100%; object-fit: cover;
|
||||
}
|
||||
.gr-overlay {
|
||||
position: absolute; inset: 0;
|
||||
background: rgba(0,0,0,0.42); z-index: 1; pointer-events: none;
|
||||
}
|
||||
#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: 'Nunito', sans-serif;
|
||||
font-weight: 900; font-size: 100px;
|
||||
display: inline-block; letter-spacing: 0.04em; line-height: 1;
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
opacity: 0.3;
|
||||
}
|
||||
</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 class="gr-overlay"></div>
|
||||
<div id="gr-container"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
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},
|
||||
];
|
||||
|
||||
// Enhanced gradient palette with accent for keywords
|
||||
var GRADS = [
|
||||
"linear-gradient(135deg,#FF6B6B,#FFD700)",
|
||||
"linear-gradient(135deg,#4ADE80,#22D3EE)",
|
||||
"linear-gradient(135deg,#818CF8,#E879F9)",
|
||||
"linear-gradient(135deg,#FFD700,#FF6B35)",
|
||||
"linear-gradient(135deg,#22D3EE,#4ADE80)",
|
||||
"linear-gradient(135deg,#E879F9,#818CF8)",
|
||||
"linear-gradient(135deg,#FF3366,#FF6B35)"
|
||||
];
|
||||
var KEYWORD_GRADS = {
|
||||
8: "linear-gradient(135deg,#FF6B6B,#FFD700,#FF6B6B)", // HyperFrames
|
||||
12: "linear-gradient(135deg,#22D3EE,#ffffff,#22D3EE)", // HTML
|
||||
15: "linear-gradient(135deg,#818CF8,#E879F9,#818CF8)", // professional
|
||||
24: "linear-gradient(135deg,#00F5D4,#22D3EE,#00F5D4)", // code
|
||||
27: "linear-gradient(135deg,#FFD700,#FF6B35,#FFD700)" // cinema.
|
||||
};
|
||||
|
||||
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 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 = '100px';
|
||||
var grad = KEYWORD_GRADS[wi] || GRADS[wi % 7];
|
||||
span.style.background = grad;
|
||||
span.style.webkitBackgroundClip = 'text';
|
||||
span.style.backgroundClip = 'text';
|
||||
span.style.color = 'transparent';
|
||||
grp.appendChild(span);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
var gs = g.start;
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var el = document.getElementById('gr-w-' + wi);
|
||||
tl.set(el, { opacity: 0, y: 55, scale: 0.35 }, gs);
|
||||
tl.fromTo(el,
|
||||
{ y: 55, scale: 0.35, opacity: 0 },
|
||||
{ y: 0, scale: 1, opacity: 0.35, duration: 0.55, ease: 'elastic.out(1.6,0.4)' },
|
||||
gs + i * 0.05
|
||||
);
|
||||
});
|
||||
|
||||
tl.set(grp, { visibility: 'visible', opacity: 1 }, gs);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var el = document.getElementById('gr-w-' + wi);
|
||||
tl.to(el, { opacity: 1, scale: 1.1, duration: 0.08 }, w.start);
|
||||
tl.to(el, { opacity: 0.3, scale: 1, duration: 0.08 }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { y: -26, opacity: 0, duration: 0.18 }, g.end - 0.18);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['gradient-fill'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<!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 || {};
|
||||
|
||||
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 = '220px';
|
||||
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['kinetic-slam'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
<!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 || {};
|
||||
|
||||
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 = '220px';
|
||||
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['kinetic-slam'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<!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 || {};
|
||||
|
||||
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.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},
|
||||
];
|
||||
|
||||
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 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 = '96px';
|
||||
|
||||
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.05);
|
||||
tl.set(scr0El, { display: 'none' }, w.start + 0.05);
|
||||
tl.set(realEl, { display: 'inline' }, w.start + 0.1);
|
||||
tl.set(scr1El, { display: 'none' }, w.start + 0.1);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['matrix-decode'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
<!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 || {};
|
||||
|
||||
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.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},
|
||||
];
|
||||
|
||||
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 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 = '96px';
|
||||
|
||||
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.05);
|
||||
tl.set(scr0El, { display: 'none' }, w.start + 0.05);
|
||||
tl.set(realEl, { display: 'inline' }, w.start + 0.1);
|
||||
tl.set(scr1El, { display: 'none' }, w.start + 0.1);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, duration: 0.12 }, g.end - 0.12);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['matrix-decode'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<!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}
|
||||
];
|
||||
|
||||
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 fontSize = BASE_FONT_SIZE;
|
||||
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["neon-accent"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
<!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}
|
||||
];
|
||||
|
||||
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 fontSize = BASE_FONT_SIZE;
|
||||
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["neon-accent"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
<!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 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 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 = '96px';
|
||||
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['neon-glow'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
<!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 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 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 = '96px';
|
||||
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['neon-glow'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<!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: 240px;
|
||||
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% 100%;
|
||||
}
|
||||
|
||||
.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 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 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;
|
||||
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 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;
|
||||
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["parallax-layers"] = tl;
|
||||
|
||||
document.fonts.ready.then(function () { applyBehindScales(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,329 @@
|
||||
<!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: 240px;
|
||||
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% 100%;
|
||||
}
|
||||
|
||||
.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 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 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;
|
||||
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 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;
|
||||
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["parallax-layers"] = tl;
|
||||
|
||||
document.fonts.ready.then(function () { applyBehindScales(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<!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 || {};
|
||||
|
||||
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 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 = '88px';
|
||||
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['particle-burst'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,212 @@
|
||||
<!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 || {};
|
||||
|
||||
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 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 = '88px';
|
||||
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['particle-burst'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
<!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["pill-karaoke"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
<!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["pill-karaoke"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Texture Lava</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>
|
||||
/* Inlined texture-mask-text component styles */
|
||||
.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));
|
||||
}
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
}
|
||||
|
||||
*, *::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;
|
||||
}
|
||||
|
||||
/* Lava-specific overrides for texture mask */
|
||||
.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="texture-lava"
|
||||
data-composition-id="caption-texture-lava"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-fps="30"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<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 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('lv-container');
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
WORDS.forEach(function(w, wi) {
|
||||
var txt = w.text.toUpperCase();
|
||||
var fontSize = 200;
|
||||
|
||||
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 hf-texture-lava lv-texture-override lv-word';
|
||||
wordEl.id = 'lv-word-' + wi;
|
||||
wordEl.textContent = txt;
|
||||
wordEl.style.fontSize = fontSize + 'px';
|
||||
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['texture-lava'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Texture Lava</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>
|
||||
/* Inlined texture-mask-text component styles */
|
||||
.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));
|
||||
}
|
||||
.hf-texture-lava {
|
||||
-webkit-mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
mask-image: url("/assets/texture-mask-text/masks/lava.png");
|
||||
}
|
||||
|
||||
*, *::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;
|
||||
}
|
||||
|
||||
/* Lava-specific overrides for texture mask */
|
||||
.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="texture-lava"
|
||||
data-composition-id="caption-texture-lava"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-fps="30"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<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 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('lv-container');
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
WORDS.forEach(function(w, wi) {
|
||||
var txt = w.text.toUpperCase();
|
||||
var fontSize = 200;
|
||||
|
||||
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 hf-texture-lava lv-texture-override lv-word';
|
||||
wordEl.id = 'lv-word-' + wi;
|
||||
wordEl.textContent = txt;
|
||||
wordEl.style.fontSize = fontSize + 'px';
|
||||
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['texture-lava'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://hyperframes.heygen.com/schema/registry-item.json",
|
||||
"name": "caption-texture-lava",
|
||||
"type": "hyperframes:component",
|
||||
"title": "Texture Lava",
|
||||
"description": "Flowing lava texture mask over large uppercase text",
|
||||
"tags": ["captions", "caption-style", "texture", "lava", "mask"],
|
||||
"files": [
|
||||
{
|
||||
"path": "caption-texture-lava.html",
|
||||
"target": "compositions/components/caption-texture-lava.html",
|
||||
"type": "hyperframes:snippet"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Typewriter</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=JetBrains+Mono: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;
|
||||
}
|
||||
#typewriter {
|
||||
position: relative; width: 1920px; height: 1080px;
|
||||
overflow: hidden; background: #000;
|
||||
}
|
||||
#tw-video {
|
||||
position: absolute; inset: 0; z-index: 0;
|
||||
width: 100%; height: 100%; object-fit: cover;
|
||||
}
|
||||
.tw-overlay {
|
||||
position: absolute; inset: 0;
|
||||
background: rgba(0,0,0,0.85); z-index: 1; pointer-events: none;
|
||||
}
|
||||
#tw-container {
|
||||
position: absolute; inset: 0; z-index: 10; pointer-events: none;
|
||||
}
|
||||
.tw-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;
|
||||
}
|
||||
.tw-word {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 700; font-size: 76px;
|
||||
color: #00FF41;
|
||||
text-shadow: 0 0 12px rgba(0,255,65,0.5);
|
||||
display: inline-block; letter-spacing: 0.04em; line-height: 1; white-space: nowrap;
|
||||
}
|
||||
.tw-char { display: none; }
|
||||
.tw-cursor {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 700; font-size: 76px;
|
||||
color: #00FF41;
|
||||
text-shadow: 0 0 12px rgba(0,255,65,0.5);
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="typewriter"
|
||||
data-composition-id="caption-typewriter"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-fps="30"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<video id="tw-video" 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>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
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.1},
|
||||
{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},
|
||||
];
|
||||
|
||||
var container = document.getElementById('tw-container');
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
GROUPS.forEach(function(g, gi) {
|
||||
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
|
||||
var fontSize = 76;
|
||||
var grp = document.createElement('div');
|
||||
grp.className = 'tw-group';
|
||||
grp.id = 'tw-grp-' + gi;
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var wordWrap = document.createElement('span');
|
||||
wordWrap.className = 'tw-word';
|
||||
wordWrap.id = 'tw-w-' + wi;
|
||||
wordWrap.style.fontSize = fontSize + 'px';
|
||||
|
||||
w.text.split('').forEach(function(c, ci) {
|
||||
var charEl = document.createElement('span');
|
||||
charEl.className = 'tw-char';
|
||||
charEl.id = 'tw-c-' + wi + '-' + ci;
|
||||
charEl.textContent = c;
|
||||
wordWrap.appendChild(charEl);
|
||||
});
|
||||
|
||||
var cursor = document.createElement('span');
|
||||
cursor.className = 'tw-cursor';
|
||||
cursor.id = 'tw-cur-' + wi;
|
||||
cursor.textContent = '_';
|
||||
cursor.style.fontSize = fontSize + 'px';
|
||||
wordWrap.appendChild(cursor);
|
||||
grp.appendChild(wordWrap);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
tl.set(grp, { visibility: 'visible', opacity: 1 }, g.start);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var chars = w.text.split('');
|
||||
var wordDuration = Math.max(w.end - w.start, 0.1);
|
||||
var charCount = chars.length;
|
||||
var cursorEl = document.getElementById('tw-cur-' + wi);
|
||||
|
||||
tl.set(cursorEl, { display: 'inline' }, w.start);
|
||||
chars.forEach(function(c, ci) {
|
||||
var charEl = document.getElementById('tw-c-' + wi + '-' + ci);
|
||||
tl.set(charEl, { display: 'inline' }, w.start + ci * (wordDuration / charCount));
|
||||
});
|
||||
tl.set(cursorEl, { display: 'none' }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, duration: 0.2 }, g.end - 0.2);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['typewriter'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=1920, height=1080" />
|
||||
<title>Typewriter</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=JetBrains+Mono: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;
|
||||
}
|
||||
#typewriter {
|
||||
position: relative; width: 1920px; height: 1080px;
|
||||
overflow: hidden; background: #000;
|
||||
}
|
||||
#tw-video {
|
||||
position: absolute; inset: 0; z-index: 0;
|
||||
width: 100%; height: 100%; object-fit: cover;
|
||||
}
|
||||
.tw-overlay {
|
||||
position: absolute; inset: 0;
|
||||
background: rgba(0,0,0,0.85); z-index: 1; pointer-events: none;
|
||||
}
|
||||
#tw-container {
|
||||
position: absolute; inset: 0; z-index: 10; pointer-events: none;
|
||||
}
|
||||
.tw-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;
|
||||
}
|
||||
.tw-word {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 700; font-size: 76px;
|
||||
color: #00FF41;
|
||||
text-shadow: 0 0 12px rgba(0,255,65,0.5);
|
||||
display: inline-block; letter-spacing: 0.04em; line-height: 1; white-space: nowrap;
|
||||
}
|
||||
.tw-char { display: none; }
|
||||
.tw-cursor {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-weight: 700; font-size: 76px;
|
||||
color: #00FF41;
|
||||
text-shadow: 0 0 12px rgba(0,255,65,0.5);
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="typewriter"
|
||||
data-composition-id="caption-typewriter"
|
||||
data-start="0"
|
||||
data-duration="8"
|
||||
data-fps="30"
|
||||
data-width="1920"
|
||||
data-height="1080"
|
||||
>
|
||||
<video id="tw-video" 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>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
window.__timelines = window.__timelines || {};
|
||||
|
||||
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.1},
|
||||
{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},
|
||||
];
|
||||
|
||||
var container = document.getElementById('tw-container');
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
|
||||
GROUPS.forEach(function(g, gi) {
|
||||
var groupWords = WORDS.slice(g.wordStart, g.wordEnd + 1);
|
||||
var fontSize = 76;
|
||||
var grp = document.createElement('div');
|
||||
grp.className = 'tw-group';
|
||||
grp.id = 'tw-grp-' + gi;
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var wordWrap = document.createElement('span');
|
||||
wordWrap.className = 'tw-word';
|
||||
wordWrap.id = 'tw-w-' + wi;
|
||||
wordWrap.style.fontSize = fontSize + 'px';
|
||||
|
||||
w.text.split('').forEach(function(c, ci) {
|
||||
var charEl = document.createElement('span');
|
||||
charEl.className = 'tw-char';
|
||||
charEl.id = 'tw-c-' + wi + '-' + ci;
|
||||
charEl.textContent = c;
|
||||
wordWrap.appendChild(charEl);
|
||||
});
|
||||
|
||||
var cursor = document.createElement('span');
|
||||
cursor.className = 'tw-cursor';
|
||||
cursor.id = 'tw-cur-' + wi;
|
||||
cursor.textContent = '_';
|
||||
cursor.style.fontSize = fontSize + 'px';
|
||||
wordWrap.appendChild(cursor);
|
||||
grp.appendChild(wordWrap);
|
||||
});
|
||||
|
||||
container.appendChild(grp);
|
||||
|
||||
tl.set(grp, { visibility: 'visible', opacity: 1 }, g.start);
|
||||
|
||||
groupWords.forEach(function(w, i) {
|
||||
var wi = g.wordStart + i;
|
||||
var chars = w.text.split('');
|
||||
var wordDuration = Math.max(w.end - w.start, 0.1);
|
||||
var charCount = chars.length;
|
||||
var cursorEl = document.getElementById('tw-cur-' + wi);
|
||||
|
||||
tl.set(cursorEl, { display: 'inline' }, w.start);
|
||||
chars.forEach(function(c, ci) {
|
||||
var charEl = document.getElementById('tw-c-' + wi + '-' + ci);
|
||||
tl.set(charEl, { display: 'inline' }, w.start + ci * (wordDuration / charCount));
|
||||
});
|
||||
tl.set(cursorEl, { display: 'none' }, w.end);
|
||||
});
|
||||
|
||||
tl.to(grp, { opacity: 0, duration: 0.2 }, g.end - 0.2);
|
||||
tl.set(grp, { opacity: 0, visibility: 'hidden' }, g.end);
|
||||
});
|
||||
|
||||
window.__timelines['typewriter'] = tl;
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<!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: 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: 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 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 (measureLineWidth(words) <= MAX_LINE_WIDTH) {
|
||||
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 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 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 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;
|
||||
line.words.forEach(function (word) {
|
||||
var wordEl = document.createElement("span");
|
||||
wordEl.textContent = word.text.toLowerCase();
|
||||
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["weight-shift"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+323
@@ -0,0 +1,323 @@
|
||||
<!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: 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: 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 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 (measureLineWidth(words) <= MAX_LINE_WIDTH) {
|
||||
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 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 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 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;
|
||||
line.words.forEach(function (word) {
|
||||
var wordEl = document.createElement("span");
|
||||
wordEl.textContent = word.text.toLowerCase();
|
||||
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["weight-shift"] = tl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -67,6 +67,66 @@
|
||||
"name": "vignette",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-pill-karaoke",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-neon-accent",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-weight-shift",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-emoji-pop",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-editorial-emphasis",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-parallax-layers",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-glitch-rgb",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-typewriter",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-matrix-decode",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-particle-burst",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-texture-lava",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-clip-wipe",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-kinetic-slam",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-gradient-fill",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "caption-neon-glow",
|
||||
"type": "hyperframes:component"
|
||||
},
|
||||
{
|
||||
"name": "instagram-follow",
|
||||
"type": "hyperframes:block"
|
||||
|
||||
Reference in New Issue
Block a user