mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-14 21:29:34 +00:00
test(regression): add editor-agent-prod regression fixture (#37)
## Summary - Adds `editor-agent-prod` regression test fixture for the producer - Tests preview/render parity for compositions with sub-compositions, GSAP timelines, overlay elements, and `data-end` attributes - Adds `styles-g` shard to regression CI workflow to run the new test - Creates comprehensive Basel Form-styled video composition with intro/outro cards, reveals, persistent overlays, and captions - Includes biomorphic SVG assets and Inter font integration for typographic consistency - Tests complex animation timing with staggered entrances, crossfades, and synchronized A-roll movements ## Test plan - [x] Regression CI passes for editor-agent-prod shard - [x] Existing regression tests unaffected - [x] Preview/render output matches expected visual and timing specifications
This commit is contained in:
@@ -47,6 +47,8 @@ jobs:
|
||||
args: "style-13-prod style-15-prod style-16-prod"
|
||||
- shard: styles-f
|
||||
args: "style-17-prod style-18-prod"
|
||||
- shard: styles-g
|
||||
args: "overlay-montage-prod"
|
||||
steps:
|
||||
- name: Checkout (with LFS)
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
docs/
|
||||
DOCS_GUIDELINES.md
|
||||
packages/producer/tests/
|
||||
|
||||
@@ -391,27 +391,22 @@ export function lintHyperframeHtml(
|
||||
}
|
||||
}
|
||||
|
||||
// #3.7: Fabricated inline base64 media — CRITICAL
|
||||
// Inline base64 audio/video data is almost always fabricated garbage that
|
||||
// won't play. Real audio files are 100KB+ when base64-encoded.
|
||||
// #3.7: Inline base64 audio/video — PROHIBITED
|
||||
// Base64 audio/video bloats file size and breaks rendering. Use URLs or relative paths.
|
||||
{
|
||||
const base64MediaRe =
|
||||
/src\s*=\s*["'](data:(?:audio|video)\/[^;]+;base64,([A-Za-z0-9+/=]{100,}))["']/gi;
|
||||
/src\s*=\s*["'](data:(?:audio|video)\/[^;]+;base64,([A-Za-z0-9+/=]{20,}))["']/gi;
|
||||
let b64Match: RegExpExecArray | null;
|
||||
while ((b64Match = base64MediaRe.exec(source)) !== null) {
|
||||
// Check if it's suspiciously repetitive (fake data has long runs of repeated chars)
|
||||
const sample = (b64Match[2] || "").slice(0, 200);
|
||||
const uniqueChars = new Set(sample.replace(/[A-Za-z0-9+/=]/g, (c) => c)).size;
|
||||
const dataSize = Math.round(((b64Match[2] || "").length * 3) / 4);
|
||||
const isSuspicious = uniqueChars < 15 || (dataSize > 1000 && dataSize < 50000);
|
||||
// Any embedded base64 audio is suspicious — real audio should be a file
|
||||
pushFinding({
|
||||
code: "fabricated_inline_media",
|
||||
severity: isSuspicious ? "error" : "warning",
|
||||
message: isSuspicious
|
||||
? `Fabricated base64 media detected (${(dataSize / 1024).toFixed(0)} KB). This is almost certainly fake data that won't play.`
|
||||
: `Embedded base64 audio/video detected (${(dataSize / 1024).toFixed(0)} KB). Consider using a file URL instead.`,
|
||||
fixHint: "Remove the data: URI and use a real media file URL instead.",
|
||||
code: "base64_media_prohibited",
|
||||
severity: "error",
|
||||
message: `Inline base64 audio/video detected (${(dataSize / 1024).toFixed(0)} KB)${isSuspicious ? " — likely fabricated data" : ""}. Base64 media is prohibited — it bloats file size and breaks rendering.`,
|
||||
fixHint: "Use a relative path (assets/music.mp3) or HTTPS URL for the audio/video src. Never embed media as base64.",
|
||||
snippet: truncateSnippet((b64Match[1] ?? "").slice(0, 80) + "..."),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "overlay-montage-prod",
|
||||
"description": "Multi-composition montage with overlays, GSAP timelines, and data-end attributes. Tests preview/render parity for composition visibility.",
|
||||
"tags": ["style-regression", "prod-style", "slow"],
|
||||
"minPsnr": 30,
|
||||
"maxFrameFailures": 0,
|
||||
"minAudioCorrelation": 0.9,
|
||||
"maxAudioLagWindows": 120,
|
||||
"renderConfig": {
|
||||
"fps": 30
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9be0476841134b2be51bd21231aee86e4bbf4eaecaa2f55b3b397d3c621889fd
|
||||
size 15000289
|
||||
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1920" width="1080" height="1920">
|
||||
<path id="biomorphic-branch" fill="#0A0A0A" d="M 520 1180
|
||||
C 510 1080, 525 980, 535 930
|
||||
C 460 910, 340 860, 310 790
|
||||
C 350 830, 440 880, 515 900
|
||||
C 540 830, 660 760, 740 730
|
||||
C 680 770, 580 820, 550 880
|
||||
C 570 810, 590 760, 580 730
|
||||
C 610 770, 580 830, 565 900
|
||||
C 555 980, 550 1080, 560 1180
|
||||
C 550 1190, 530 1190, 520 1180
|
||||
Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 541 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1920" width="1080" height="1920">
|
||||
<path fill="#0A0A0A" d="M 440 670 C 410 800, 470 900, 450 1050 C 430 1180, 420 1230, 420 1250 C 480 1260, 600 1240, 660 1250 C 690 1100, 620 950, 650 800 C 670 680, 640 670, 640 670 C 580 660, 500 680, 440 670 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1920" width="1080" height="1920">
|
||||
<path fill="#0A0A0A" d="M 540 710 C 700 710, 790 800, 790 960 C 790 1120, 700 1210, 540 1210 C 450 1210, 550 1050, 400 1000 C 250 950, 350 1100, 290 960 C 230 820, 380 710, 540 710 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 287 B |
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1920" width="1080" height="1920">
|
||||
<g id="scatter-grid" fill="#0A0A0A">
|
||||
<!-- Row 1 -->
|
||||
<circle cx="405" cy="775" r="15" />
|
||||
<circle cx="540" cy="775" r="15" />
|
||||
<circle cx="675" cy="775" r="15" />
|
||||
<!-- Row 2 -->
|
||||
<circle cx="405" cy="898.3" r="15" />
|
||||
<circle cx="540" cy="898.3" r="15" />
|
||||
<circle cx="675" cy="898.3" r="15" />
|
||||
<!-- Row 3 -->
|
||||
<circle cx="405" cy="1021.7" r="15" />
|
||||
<circle cx="540" cy="1021.7" r="15" />
|
||||
<circle cx="675" cy="1021.7" r="15" />
|
||||
<!-- Row 4 -->
|
||||
<circle cx="405" cy="1145" r="15" />
|
||||
<circle cx="540" cy="1145" r="15" />
|
||||
<circle cx="675" cy="1145" r="15" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1080 1920" width="1080" height="1920">
|
||||
<g transform="translate(290, 710)">
|
||||
<path fill="#0A0A0A" d="M 250 60
|
||||
C 300 60, 320 150, 380 180
|
||||
C 450 210, 480 280, 420 320
|
||||
C 380 350, 320 300, 280 380
|
||||
C 250 450, 180 480, 140 420
|
||||
C 100 360, 180 320, 150 260
|
||||
C 120 200, 50 180, 80 120
|
||||
C 110 60, 180 60, 250 60 Z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 474 B |
@@ -0,0 +1,228 @@
|
||||
<template id="intro-outro-template">
|
||||
<div data-composition-id="intro-outro" data-width="1080" data-height="1920" data-duration="42.36">
|
||||
|
||||
<!-- Intro Card (0s to 3s) -->
|
||||
<div class="card intro-card" id="intro-card">
|
||||
<div class="biomorphic-container upper-right">
|
||||
<img src="assets/splat.svg" class="biomorphic-form" id="intro-splat" />
|
||||
</div>
|
||||
|
||||
<div class="text-content">
|
||||
<h1 class="title">first week</h1>
|
||||
<div class="rule"></div>
|
||||
<h2 class="subtitle">onboarding guide</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Outro Card (39.36s to 42.36s) -->
|
||||
<div class="card outro-card" id="outro-card">
|
||||
<div class="biomorphic-container lower-left">
|
||||
<img src="assets/branch.svg" class="biomorphic-form branch-white" id="outro-branch" />
|
||||
</div>
|
||||
|
||||
<div class="text-content-outro">
|
||||
<h1 class="cta-text">get started</h1>
|
||||
</div>
|
||||
|
||||
<div class="biomorphic-container upper-right-outro">
|
||||
<img src="assets/scatter.svg" class="biomorphic-form scatter-white" id="outro-scatter" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="intro-outro"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .card {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Intro Styles */
|
||||
[data-composition-id="intro-outro"] .intro-card {
|
||||
background-color: #F5F5F0; /* hf-white */
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .biomorphic-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .upper-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .biomorphic-form {
|
||||
width: 80%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .text-content {
|
||||
position: absolute;
|
||||
left: 80px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .title {
|
||||
font-size: 80px;
|
||||
font-weight: 700;
|
||||
color: #0A0A0A; /* hf-black */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
text-transform: lowercase;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .rule {
|
||||
width: 324px; /* 30% of 1080 */
|
||||
height: 1px;
|
||||
background-color: #0A0A0A;
|
||||
margin: 20px 0;
|
||||
transform-origin: left center;
|
||||
transform: scaleX(0);
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .subtitle {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: #0A0A0A;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
/* Outro Styles */
|
||||
[data-composition-id="intro-outro"] .outro-card {
|
||||
background-color: #0A0A0A; /* hf-black */
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .lower-left {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .upper-right-outro {
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .branch-white,
|
||||
[data-composition-id="intro-outro"] .scatter-white {
|
||||
filter: invert(98%) sepia(4%) saturate(144%) hue-rotate(20deg) brightness(101%) contrast(94%);
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .text-content-outro {
|
||||
position: absolute;
|
||||
left: 80px;
|
||||
top: 25%; /* Upper half of frame */
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
[data-composition-id="intro-outro"] .cta-text {
|
||||
font-size: 64px;
|
||||
font-weight: 700;
|
||||
color: #F5F5F0; /* hf-white */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
const intro = '#intro-card';
|
||||
const outro = '#outro-card';
|
||||
|
||||
// --- Intro Card (0s to 3s) ---
|
||||
tl.set(intro, { display: 'block', opacity: 1 }, 0);
|
||||
|
||||
// Entrance
|
||||
tl.fromTo('#intro-splat',
|
||||
{ scale: 0.4 },
|
||||
{ scale: 1, duration: 0.7, ease: 'power1.out' },
|
||||
0
|
||||
);
|
||||
|
||||
tl.fromTo('[data-composition-id="intro-outro"] .title',
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1, duration: 0.4 },
|
||||
0.3
|
||||
);
|
||||
|
||||
tl.to('[data-composition-id="intro-outro"] .rule',
|
||||
{ scaleX: 1, duration: 0.3, ease: 'none' },
|
||||
0.5
|
||||
);
|
||||
|
||||
tl.fromTo('[data-composition-id="intro-outro"] .subtitle',
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1, duration: 0.4 },
|
||||
0.6
|
||||
);
|
||||
|
||||
// Exit
|
||||
tl.to(intro, { opacity: 0, duration: 0.3 }, 2.7);
|
||||
tl.set(intro, { display: 'none' }, 3.0);
|
||||
|
||||
// --- Outro Card (39.36s to 42.36s) ---
|
||||
const outroStart = 39.36;
|
||||
|
||||
tl.set(outro, { display: 'block', opacity: 1 }, outroStart);
|
||||
|
||||
// Entrance mirrors intro timing
|
||||
tl.fromTo('#outro-branch',
|
||||
{ scale: 0.4 },
|
||||
{ scale: 1, duration: 0.7, ease: 'power1.out' },
|
||||
outroStart
|
||||
);
|
||||
|
||||
tl.fromTo('#outro-scatter',
|
||||
{ scale: 0.4 },
|
||||
{ scale: 1, duration: 0.7, ease: 'power1.out' },
|
||||
outroStart
|
||||
);
|
||||
|
||||
tl.fromTo('.cta-text',
|
||||
{ opacity: 0 },
|
||||
{ opacity: 1, duration: 0.4 },
|
||||
outroStart + 0.3
|
||||
);
|
||||
|
||||
// Buffer at end
|
||||
tl.to({}, { duration: 2 }, 42.36);
|
||||
|
||||
window.__timelines["intro-outro"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
<template id="persistent-overlays-template">
|
||||
<div data-composition-id="persistent-overlays" data-width="1080" data-height="1920" data-duration="39.36">
|
||||
<!-- Option A: Notch (Top-Left) -->
|
||||
<div class="overlay notch-container">
|
||||
<img src="assets/notch.svg" class="svg-icon notch-icon" />
|
||||
</div>
|
||||
|
||||
<!-- Option B: Scatter (Bottom-Right) -->
|
||||
<div class="overlay scatter-container">
|
||||
<img src="assets/scatter.svg" class="svg-icon scatter-icon" />
|
||||
</div>
|
||||
|
||||
<!-- Option A: Column (Top-Right) -->
|
||||
<div class="overlay column-container">
|
||||
<img src="assets/column.svg" class="svg-icon column-icon" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="persistent-overlays"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-composition-id="persistent-overlays"] .overlay {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
[data-composition-id="persistent-overlays"] .svg-icon {
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
/* Basel Form Style: Strict monochromatic palette */
|
||||
/* Using filter to ensure #0A0A0A (hf-black) if SVG is not already black */
|
||||
filter: brightness(0) saturate(100%) invert(4%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(4%) contrast(100%);
|
||||
}
|
||||
|
||||
/* Positioning - Asymmetrical, bleeding off edges */
|
||||
[data-composition-id="persistent-overlays"] .notch-container {
|
||||
top: -50px;
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
[data-composition-id="persistent-overlays"] .scatter-container {
|
||||
bottom: 120px;
|
||||
right: -40px;
|
||||
}
|
||||
|
||||
[data-composition-id="persistent-overlays"] .column-container {
|
||||
top: 200px;
|
||||
right: -100px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// 3.0s to 15.0s: Option A - Notch (Top-Left)
|
||||
// Fades in at 3.0s over 0.4s. Fades out at 15.0s over 0.4s.
|
||||
tl.to('.notch-container', {
|
||||
opacity: 1,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 3.0);
|
||||
tl.to('.notch-container', {
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 15.0);
|
||||
|
||||
// 15.0s to 30.0s: Option B - Scatter (Bottom-Right)
|
||||
// Fades in at 15.0s over 0.4s. Fades out at 30.0s over 0.4s.
|
||||
tl.to('.scatter-container', {
|
||||
opacity: 1,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 15.0);
|
||||
tl.to('.scatter-container', {
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 30.0);
|
||||
|
||||
// 30.0s to 39.36s: Option A - Column (Top-Right)
|
||||
// Fades in at 30.0s over 0.4s. Fades out at 39.36s over 0.4s.
|
||||
tl.to('.column-container', {
|
||||
opacity: 1,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 30.0);
|
||||
tl.to('.column-container', {
|
||||
opacity: 0,
|
||||
duration: 0.4,
|
||||
ease: 'power2.inOut'
|
||||
}, 39.36 - 0.4); // Ends at 39.36s
|
||||
|
||||
window.__timelines["persistent-overlays"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,160 @@
|
||||
<template id="reveal1-template">
|
||||
<div data-composition-id="reveal1" data-width="1080" data-height="1920" data-start="4.619" data-duration="3.381">
|
||||
<div class="reveal-bg"></div>
|
||||
|
||||
<div class="content-wrapper">
|
||||
<div class="text-group">
|
||||
<h1 class="title">activate accounts</h1>
|
||||
<div class="rule-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-container">
|
||||
<img src="assets/notch.svg" class="primary-form form-1" alt="Form 1">
|
||||
<img src="assets/branch.svg" class="primary-form form-2" alt="Form 2">
|
||||
<img src="assets/scatter.svg" class="primary-form form-3" alt="Form 3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="reveal1"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background-color: #F5F5F0; /* hf-white */
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .reveal-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #F5F5F0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .content-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .text-group {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .title {
|
||||
font-size: 80px;
|
||||
font-weight: 700;
|
||||
color: #0A0A0A; /* hf-black */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
line-height: 1.1;
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .rule-line {
|
||||
width: 0%;
|
||||
height: 1px;
|
||||
background-color: #0A0A0A;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .form-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .primary-form {
|
||||
position: absolute;
|
||||
transform: scale(0.5);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .form-1 {
|
||||
top: 350px;
|
||||
right: 150px;
|
||||
width: 450px;
|
||||
height: 450px;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .form-2 {
|
||||
top: 650px;
|
||||
right: 350px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
[data-composition-id="reveal1"] .form-3 {
|
||||
top: 850px;
|
||||
right: 100px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Selectors scoped to this composition
|
||||
const comp = document.querySelector('[data-composition-id="reveal1"]');
|
||||
const forms = comp.querySelectorAll('.primary-form');
|
||||
const title = comp.querySelector('.title');
|
||||
const rule = comp.querySelector('.rule-line');
|
||||
const aroll = document.querySelector('#aroll-video');
|
||||
|
||||
// --- ENTRANCE (Starts at 4.619s relative to master, 0s relative to this comp) ---
|
||||
|
||||
// Forms scale from 0.5 to 1.0 over 0.5s (power1.out)
|
||||
tl.to(forms, {
|
||||
scale: 1,
|
||||
duration: 0.5,
|
||||
stagger: 0.1,
|
||||
ease: 'power1.out'
|
||||
}, 0);
|
||||
|
||||
// Text fades in over 0.3s, starting 0.2s after form begins scaling
|
||||
tl.to(title, {
|
||||
opacity: 1,
|
||||
duration: 0.3,
|
||||
ease: 'none'
|
||||
}, 0.2);
|
||||
|
||||
// Rule draws on from left (0.3s, power1.out, 0.1s delay after text)
|
||||
tl.to(rule, {
|
||||
width: '100%',
|
||||
duration: 0.3,
|
||||
ease: 'power1.out'
|
||||
}, 0.6); // 0.2 (text start) + 0.3 (text duration) + 0.1 (delay) = 0.6
|
||||
|
||||
// --- EXIT (Starts at 7.7s relative to master, 7.7 - 4.619 = 3.081s in this comp) ---
|
||||
// All elements fade to 0 over 0.3s simultaneously (power1.in)
|
||||
tl.to([...forms, title, rule, comp.querySelector('.reveal-bg')], {
|
||||
opacity: 0,
|
||||
duration: 0.3,
|
||||
ease: 'power1.in'
|
||||
}, 3.081);
|
||||
|
||||
// Register timeline
|
||||
window.__timelines["reveal1"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,130 @@
|
||||
<template id="reveal2-template">
|
||||
<div data-composition-id="reveal2" data-width="1080" data-height="1920" data-start="15.439" data-duration="3.561">
|
||||
<div class="reveal2-bg"></div>
|
||||
<div class="reveal2-form-container">
|
||||
<img src="assets/column.svg" class="reveal2-form form-1" alt="Column Form">
|
||||
<img src="assets/splat.svg" class="reveal2-form form-2" alt="Splat Form">
|
||||
<img src="assets/notch.svg" class="reveal2-form form-3" alt="Notch Form">
|
||||
</div>
|
||||
<div class="reveal2-text">compliance training</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="reveal2"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.reveal2-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #0A0A0A; /* hf-black */
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.reveal2-form-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reveal2-form {
|
||||
position: absolute;
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
/* Ensure the SVG is white */
|
||||
filter: brightness(0) invert(1) sepia(1) saturate(0) hue-rotate(0deg) brightness(0.96) contrast(1);
|
||||
}
|
||||
|
||||
.form-1 {
|
||||
top: 300px;
|
||||
left: 100px;
|
||||
width: 400px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.form-2 {
|
||||
top: 600px;
|
||||
left: 250px;
|
||||
width: 450px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.form-3 {
|
||||
top: 950px;
|
||||
left: 80px;
|
||||
width: 300px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.reveal2-text {
|
||||
position: absolute;
|
||||
top: 200px;
|
||||
right: 80px;
|
||||
width: 400px;
|
||||
font-size: 80px;
|
||||
font-weight: 700;
|
||||
color: #F5F5F0; /* hf-white */
|
||||
text-align: left;
|
||||
line-height: 1.1;
|
||||
opacity: 0;
|
||||
text-transform: lowercase;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Entrance (starts at 15.439s, which is 0s in this composition's local timeline)
|
||||
// Background crossfades from white to black over 0.3s
|
||||
tl.to('.reveal2-bg', {
|
||||
opacity: 1,
|
||||
duration: 0.3,
|
||||
ease: 'none'
|
||||
}, 0);
|
||||
|
||||
// Form scales from 0.5 to 1.0 over 0.5s (power1.out)
|
||||
tl.to('.reveal2-form', {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
duration: 0.5,
|
||||
stagger: 0.1,
|
||||
ease: 'power1.out'
|
||||
}, 0);
|
||||
|
||||
// Text fades in over 0.3s, starting 0.2s after form begins scaling
|
||||
tl.to('.reveal2-text', {
|
||||
opacity: 1,
|
||||
duration: 0.3,
|
||||
ease: 'power1.out'
|
||||
}, 0.2);
|
||||
|
||||
// Hold: completely static (handled by the gap between entrance and exit)
|
||||
|
||||
// Exit (starts at 18.7s, which is 18.7 - 15.439 = 3.261s in local timeline)
|
||||
const exitTime = 18.7 - 15.439;
|
||||
|
||||
// All elements fade to 0 over 0.3s simultaneously (power1.in)
|
||||
tl.to(['.reveal2-bg', '.reveal2-form', '.reveal2-text'], {
|
||||
opacity: 0,
|
||||
duration: 0.3,
|
||||
ease: 'power1.in'
|
||||
}, exitTime);
|
||||
|
||||
window.__timelines["reveal2"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,118 @@
|
||||
<template id="reveal3-template">
|
||||
<div data-composition-id="reveal3" data-width="1080" data-height="1920" data-start="24.079" data-duration="3.921">
|
||||
<div class="reveal3-bg"></div>
|
||||
<div class="reveal3-form-container">
|
||||
<img src="assets/splat.svg" class="reveal3-form form-1" />
|
||||
<img src="assets/branch.svg" class="reveal3-form form-2" />
|
||||
<img src="assets/column.svg" class="reveal3-form form-3" />
|
||||
</div>
|
||||
<div class="reveal3-text">schedule 1:1</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="reveal3"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
background-color: #F5F5F0; /* hf-white */
|
||||
}
|
||||
|
||||
.reveal3-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #F5F5F0;
|
||||
}
|
||||
|
||||
.reveal3-form-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.reveal3-form {
|
||||
position: absolute;
|
||||
object-fit: contain;
|
||||
/* SVG fill is handled via CSS filter to ensure #0A0A0A if not already in SVG */
|
||||
filter: brightness(0) saturate(100%) invert(4%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(96%) contrast(96%);
|
||||
}
|
||||
|
||||
.form-1 {
|
||||
top: 250px;
|
||||
left: 50%;
|
||||
margin-left: -200px; /* Instead of transform to avoid GSAP conflict */
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.form-2 {
|
||||
top: 550px;
|
||||
left: 50%;
|
||||
margin-left: -400px;
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.form-3 {
|
||||
top: 600px;
|
||||
left: 50%;
|
||||
margin-left: 100px;
|
||||
width: 350px;
|
||||
height: 350px;
|
||||
}
|
||||
|
||||
.reveal3-text {
|
||||
position: absolute;
|
||||
bottom: 80px;
|
||||
left: 80px;
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 80px;
|
||||
color: #0A0A0A; /* hf-black */
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Reveal 3 (24.079s to 28.0s)
|
||||
// Duration: 28.0 - 24.079 = 3.921s
|
||||
|
||||
// Entrance (starts at 24.079s, which is 0s in this composition's local timeline)
|
||||
// Form scales from 0.5 to 1.0 over 0.5s (power1.out)
|
||||
tl.fromTo('.reveal3-form',
|
||||
{ scale: 0.5 },
|
||||
{ scale: 1.0, duration: 0.5, stagger: 0.1, ease: 'power1.out' },
|
||||
0
|
||||
);
|
||||
|
||||
// Text fades in over 0.3s, starting 0.2s after form begins scaling
|
||||
tl.to('.reveal3-text',
|
||||
{ opacity: 1, duration: 0.3, ease: 'power1.out' },
|
||||
0.2
|
||||
);
|
||||
|
||||
// Exit (starts at 27.7s)
|
||||
// Local time for exit: 27.7 - 24.079 = 3.621s
|
||||
// All elements fade to 0 over 0.3s simultaneously (power1.in)
|
||||
tl.to(['.reveal3-form', '.reveal3-text', '.reveal3-bg'],
|
||||
{ opacity: 0, duration: 0.3, ease: 'power1.in' },
|
||||
3.621
|
||||
);
|
||||
|
||||
window.__timelines["reveal3"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=1080, height=1920, initial-scale=1.0">
|
||||
<title>Basel Form Video</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
|
||||
<style>
|
||||
/*
|
||||
* BASEL FORM (ARMIN HOFMANN)
|
||||
* Strict monochromatic, biomorphic, typographic composition
|
||||
*/
|
||||
:root {
|
||||
--hf-black: #0A0A0A;
|
||||
--hf-white: #F5F5F0;
|
||||
--hf-gray: #6B6B6B;
|
||||
--frame-width: 1080px;
|
||||
--frame-height: 1920px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: var(--frame-width);
|
||||
height: var(--frame-height);
|
||||
background-color: var(--hf-white);
|
||||
font-family: 'Inter', sans-serif;
|
||||
overflow: hidden;
|
||||
color: var(--hf-black);
|
||||
}
|
||||
|
||||
#container {
|
||||
width: var(--frame-width);
|
||||
height: var(--frame-height);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* TRACK 1: REVEALS (Behind A-roll) */
|
||||
#track-1-reveals {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* TRACK 0: A-ROLL */
|
||||
#track-0-aroll {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 20;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#aroll-video {
|
||||
width: 75%;
|
||||
height: auto;
|
||||
filter: saturate(0) contrast(1.3);
|
||||
}
|
||||
|
||||
/* TRACK 2: OVERLAYS (Foreground) */
|
||||
#track-2-overlays {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 30;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* TRACK 3: CAPTIONS */
|
||||
#track-3-captions {
|
||||
position: absolute;
|
||||
top: 1365px; /* 555px from bottom */
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
z-index: 40;
|
||||
/* Caption placement y-conflict checked: well below face zone (263-736) */
|
||||
}
|
||||
|
||||
.caption-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.caption-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--hf-black);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.caption-text {
|
||||
position: relative;
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: var(--hf-white);
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: lowercase;
|
||||
padding: 10px 20px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* INTRO & OUTRO */
|
||||
.full-frame-card {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 50; /* Above everything */
|
||||
background-color: var(--hf-white);
|
||||
}
|
||||
|
||||
.outro-card {
|
||||
background-color: var(--hf-black);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container" data-composition-id="root" data-width="1080" data-height="1920" data-duration="42.36">
|
||||
<!-- TRACK 1: Reveals (Behind A-roll) -->
|
||||
<div id="track-1-reveals" style="z-index: 10; position: absolute; width: 100%; height: 100%;">
|
||||
<div data-composition-src="compositions/reveal1.html" data-start="4.619" data-track-index="1"></div>
|
||||
<div data-composition-src="compositions/reveal2.html" data-start="15.439" data-track-index="1"></div>
|
||||
<div data-composition-src="compositions/reveal3.html" data-start="24.079" data-track-index="1"></div>
|
||||
</div>
|
||||
|
||||
<!-- TRACK 0: A-Roll Video -->
|
||||
<div id="track-0-aroll" style="z-index: 20; position: absolute; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;">
|
||||
<video id="aroll-video" data-start="0" data-track-index="0" src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/cd544f35_70b817304d84455490962587aa52c167.mp4" playsinline></video>
|
||||
</div>
|
||||
|
||||
<!-- TRACK 2: Overlays -->
|
||||
<div id="track-2-overlays" style="z-index: 30; position: absolute; width: 100%; height: 100%; pointer-events: none;">
|
||||
<div data-composition-src="compositions/persistent_overlays.html" data-start="0" data-track-index="2"></div>
|
||||
</div>
|
||||
|
||||
<!-- TRACK 3: Captions -->
|
||||
<div id="track-3-captions" style="z-index: 40; position: absolute; top: 1365px; left: 0; width: 100%; display: flex; justify-content: center;">
|
||||
<div class="caption-container">
|
||||
<div class="caption-bg"></div>
|
||||
<div class="caption-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Intro/Outro -->
|
||||
<div data-composition-src="compositions/intro_outro.html" data-start="0" data-track-index="50" style="z-index: 50; position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none;"></div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Transcript Data
|
||||
const transcript = [
|
||||
{'text': 'Welcome', 'start': 0.34, 'end': 0.659}, {'text': 'to', 'start': 0.74, 'end': 0.779}, {'text': 'your', 'start': 0.819, 'end': 0.919}, {'text': 'first', 'start': 0.959, 'end': 1.199}, {'text': 'week.', 'start': 1.259, 'end': 1.539},
|
||||
{'text': 'There', 'start': 2.059, 'end': 2.18}, {'text': 'are', 'start': 2.22, 'end': 2.299}, {'text': 'a', 'start': 2.339, 'end': 2.419}, {'text': 'few', 'start': 2.46, 'end': 2.559}, {'text': 'things', 'start': 2.619, 'end': 2.819}, {'text': 'you', 'start': 2.859, 'end': 2.919}, {'text': 'need', 'start': 2.939, 'end': 3.039}, {'text': 'to', 'start': 3.079, 'end': 3.139}, {'text': 'know', 'start': 3.199, 'end': 3.279}, {'text': 'to', 'start': 3.339, 'end': 3.379}, {'text': 'get', 'start': 3.439, 'end': 3.559}, {'text': 'set', 'start': 3.639, 'end': 3.779}, {'text': 'up.', 'start': 3.859, 'end': 4.039},
|
||||
{'text': 'Step', 'start': 4.619, 'end': 4.859}, {'text': 'one:', 'start': 4.96, 'end': 5.179}, {'text': 'activate', 'start': 5.42, 'end': 5.799}, {'text': 'your', 'start': 5.859, 'end': 5.92}, {'text': 'accounts.', 'start': 5.96, 'end': 6.48},
|
||||
{'text': 'You', 'start': 7.0, 'end': 7.079}, {'text': 'should', 'start': 7.119, 'end': 7.239}, {'text': 'have', 'start': 7.279, 'end': 7.359}, {'text': 'received', 'start': 7.399, 'end': 7.739}, {'text': 'an', 'start': 7.779, 'end': 7.859}, {'text': 'email', 'start': 7.94, 'end': 8.22}, {'text': 'with', 'start': 8.279, 'end': 8.42}, {'text': 'links', 'start': 8.5, 'end': 8.699}, {'text': 'to', 'start': 8.76, 'end': 8.819}, {'text': 'Slack,', 'start': 8.92, 'end': 9.319}, {'text': 'your', 'start': 9.639, 'end': 9.739}, {'text': 'email,', 'start': 9.84, 'end': 10.219}, {'text': 'and', 'start': 10.519, 'end': 10.679}, {'text': 'our', 'start': 10.859, 'end': 10.96}, {'text': 'project', 'start': 11.079, 'end': 11.5}, {'text': 'management', 'start': 11.559, 'end': 11.899}, {'text': 'tool.', 'start': 12.0, 'end': 12.259},
|
||||
{'text': 'Get', 'start': 12.799, 'end': 12.88}, {'text': 'those', 'start': 12.96, 'end': 13.18}, {'text': 'set', 'start': 13.239, 'end': 13.38}, {'text': 'up', 'start': 13.439, 'end': 13.539}, {'text': 'before', 'start': 13.599, 'end': 13.88}, {'text': 'anything', 'start': 14.039, 'end': 14.38}, {'text': 'else.', 'start': 14.46, 'end': 14.799},
|
||||
{'text': 'Step', 'start': 15.439, 'end': 15.639}, {'text': 'two:', 'start': 15.759, 'end': 16.079}, {'text': 'complete', 'start': 16.379, 'end': 16.76}, {'text': 'your', 'start': 16.799, 'end': 16.879}, {'text': 'compliance', 'start': 16.94, 'end': 17.359}, {'text': 'training.', 'start': 17.44, 'end': 17.84},
|
||||
{'text': "It's", 'start': 18.319, 'end': 18.46}, {'text': 'in', 'start': 18.479, 'end': 18.54}, {'text': 'the', 'start': 18.579, 'end': 18.62}, {'text': 'learning', 'start': 18.68, 'end': 18.959}, {'text': 'portal', 'start': 19.039, 'end': 19.299}, {'text': 'under', 'start': 19.42, 'end': 19.579}, {'text': 'required', 'start': 19.639, 'end': 20.079}, {'text': 'courses,', 'start': 20.159, 'end': 20.579}, {'text': 'and', 'start': 21.0, 'end': 21.1}, {'text': 'it', 'start': 21.139, 'end': 21.219}, {'text': 'needs', 'start': 21.299, 'end': 21.52}, {'text': 'to', 'start': 21.52, 'end': 21.6}, {'text': 'be', 'start': 21.639, 'end': 21.779}, {'text': 'done', 'start': 21.84, 'end': 22.039}, {'text': 'within', 'start': 22.1, 'end': 22.36}, {'text': 'your', 'start': 22.399, 'end': 22.539}, {'text': 'first', 'start': 22.559, 'end': 22.779}, {'text': 'five', 'start': 22.859, 'end': 23.039}, {'text': 'days.', 'start': 23.1, 'end': 23.479},
|
||||
{'text': 'Step', 'start': 24.079, 'end': 24.299}, {'text': 'three:', 'start': 24.42, 'end': 25.059}, {'text': 'schedule', 'start': 25.159, 'end': 25.5}, {'text': 'a', 'start': 25.559, 'end': 25.599}, {'text': 'one-on-one', 'start': 25.68, 'end': 26.219}, {'text': 'with', 'start': 26.26, 'end': 26.379}, {'text': 'your', 'start': 26.399, 'end': 26.499}, {'text': 'manager.', 'start': 26.539, 'end': 26.959},
|
||||
{'text': 'This', 'start': 27.539, 'end': 27.68}, {'text': 'is', 'start': 27.719, 'end': 27.86}, {'text': 'where', 'start': 27.879, 'end': 28.02}, {'text': "you'll", 'start': 28.079, 'end': 28.219}, {'text': 'walk', 'start': 28.239, 'end': 28.44}, {'text': 'through', 'start': 28.5, 'end': 28.639}, {'text': 'your', 'start': 28.659, 'end': 28.84}, {'text': 'thirty/sixty/ninety-day', 'start': 28.859, 'end': 29.939}, {'text': 'plan', 'start': 30.059, 'end': 30.639}, {'text': 'and', 'start': 30.719, 'end': 30.86}, {'text': 'get', 'start': 30.959, 'end': 31.119}, {'text': 'clarity', 'start': 31.199, 'end': 31.699}, {'text': 'on', 'start': 31.739, 'end': 31.92}, {'text': 'what', 'start': 31.939, 'end': 32.119}, {'text': 'success', 'start': 32.159, 'end': 32.759}, {'text': 'looks', 'start': 32.819, 'end': 33.02}, {'text': 'like', 'start': 33.059, 'end': 33.18}, {'text': 'in', 'start': 33.2, 'end': 33.279}, {'text': 'your', 'start': 33.319, 'end': 33.419}, {'text': 'first', 'start': 33.459, 'end': 33.659}, {'text': 'quarter.', 'start': 33.739, 'end': 34.099},
|
||||
{'text': 'If', 'start': 34.619, 'end': 34.739}, {'text': 'you', 'start': 34.759, 'end': 34.84}, {'text': 'get', 'start': 34.88, 'end': 35.02}, {'text': 'stuck', 'start': 35.059, 'end': 35.259}, {'text': 'on', 'start': 35.299, 'end': 35.459}, {'text': 'any', 'start': 35.5, 'end': 35.659}, {'text': 'of', 'start': 35.68, 'end': 35.779}, {'text': 'this,', 'start': 35.819, 'end': 36.079}, {'text': 'your', 'start': 36.38, 'end': 36.479}, {'text': 'onboarding', 'start': 36.54, 'end': 37.02}, {'text': 'buddy', 'start': 37.079, 'end': 37.38}, {'text': 'is', 'start': 37.439, 'end': 37.599}, {'text': 'your', 'start': 37.639, 'end': 37.779}, {'text': 'first', 'start': 37.799, 'end': 38.059}, {'text': 'call.', 'start': 38.119, 'end': 38.819},
|
||||
{'text': "Don't", 'start': 38.879, 'end': 39.06}, {'text': 'sit', 'start': 39.099, 'end': 39.2}, {'text': 'on', 'start': 39.259, 'end': 39.379}, {'text': 'questions.', 'start': 39.459, 'end': 40.219}, {'text': 'Ask', 'start': 40.319, 'end': 40.54}, {'text': 'early', 'start': 40.599, 'end': 40.999}, {'text': 'and', 'start': 41.059, 'end': 41.2}, {'text': 'ask', 'start': 41.279, 'end': 41.479}, {'text': 'often.', 'start': 41.579, 'end': 41.979}
|
||||
];
|
||||
|
||||
// Group words into lines of 3
|
||||
const captionLines = [];
|
||||
let currentLine = [];
|
||||
|
||||
transcript.forEach(word => {
|
||||
if (currentLine.length >= 3) {
|
||||
captionLines.push(currentLine);
|
||||
currentLine = [word];
|
||||
} else {
|
||||
currentLine.push(word);
|
||||
}
|
||||
});
|
||||
if (currentLine.length > 0) {
|
||||
captionLines.push(currentLine);
|
||||
}
|
||||
|
||||
const captionContainer = document.querySelector('.caption-container');
|
||||
const captionBg = document.querySelector('.caption-bg');
|
||||
const captionText = document.querySelector('.caption-text');
|
||||
|
||||
// Main Timeline
|
||||
window.__timelines = window.__timelines || {};
|
||||
const rootTl = gsap.timeline({ paused: true });
|
||||
|
||||
// Build Caption Animations
|
||||
captionLines.forEach((line, i) => {
|
||||
const start = line[0].start;
|
||||
const end = line[line.length - 1].end;
|
||||
const text = line.map(w => w.text).join(' ');
|
||||
|
||||
// Entrance (instant)
|
||||
rootTl.set(captionText, { textContent: text }, start);
|
||||
rootTl.set([captionBg, captionText], { opacity: 1 }, start);
|
||||
|
||||
// Exit (instant)
|
||||
rootTl.set([captionBg, captionText], { opacity: 0 }, end);
|
||||
});
|
||||
|
||||
// A-roll Reveal Animations
|
||||
const aroll = document.getElementById('aroll-video');
|
||||
|
||||
// Reveal 1 (4.619s to 8.0s) - Slide Left
|
||||
rootTl.to(aroll, { x: -1080, duration: 0.8, ease: "power1.inOut" }, 4.619);
|
||||
rootTl.to(aroll, { x: 0, duration: 0.8, ease: "power1.inOut" }, 7.2);
|
||||
|
||||
// Reveal 2 (15.439s to 19.0s) - Slide Right
|
||||
rootTl.to(aroll, { x: 1080, duration: 0.8, ease: "power1.inOut" }, 15.439);
|
||||
rootTl.to(aroll, { x: 0, duration: 0.8, ease: "power1.inOut" }, 18.2);
|
||||
|
||||
// Reveal 3 (24.079s to 28.0s) - Slide Up
|
||||
rootTl.to(aroll, { y: -1920, duration: 0.8, ease: "power1.inOut" }, 24.079);
|
||||
rootTl.to(aroll, { y: 0, duration: 0.8, ease: "power1.inOut" }, 27.2);
|
||||
|
||||
// Ensure timeline spans full duration
|
||||
rootTl.to({}, { duration: 0.1 }, 42.36);
|
||||
|
||||
window.__timelines["root"] = rootTl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user