style: apply oxfmt baseline formatting across all source files (#25)

## Summary
- Run `oxfmt .` across the entire codebase to establish formatted baseline
- 299 files changed — mechanical formatting only, no logic changes
- Double quotes, semicolons, 2-space indent, trailing commas, 100 print width

Part 3/4 of [VA-851](https://linear.app/heygen/issue/VA-851/pre-migration-configure-eslint-prettier-and-conventional-commits)

## Test plan
- [x] `pnpm format:check` — all 426 files pass
- [x] `pnpm -r typecheck` — all packages pass
- [x] `pnpm build` — all packages build
- [x] All 348 tests pass
This commit is contained in:
Vance Ingalls
2026-03-23 17:15:14 -07:00
committed by GitHub
parent 323ff8f860
commit 20be2ea1c2
299 changed files with 27750 additions and 16792 deletions
@@ -1,6 +1,7 @@
# HyperFrame Schema Compliance Review
## Executive Summary
- Total files reviewed: 5
- Critical issues: 1
- Overall compliance status: NEEDS_WORK
@@ -8,12 +9,14 @@
## Critical Issues
### Empty Tween for Duration
- **File**: compositions/captions.html:98
- **Violation**: `tl.to({}, { duration: 2 });`
- **Schema Rule**: "**NEVER create empty tweens** like `tl.to({}, { duration: N })` just to set duration — use `data-duration` instead"
- **Impact**: Violates the declarative timing model. The framework uses `data-duration` to determine the composition's length on the master timeline.
## Compliance Checklist
- [x] All compositions have `data-width` and `data-height` attributes
- [x] All timelines are finite with duration > 0
- [x] All compositions registered in `window.__timelines`
@@ -33,34 +36,44 @@
## File-Specific Reviews
### index.html
**Status**: COMPLIANT
- Correctly uses `data-composition-id`, `data-width`, `data-height`, and `data-duration`.
- Correctly loads sub-compositions using `data-composition-src`.
- Registers `master` timeline in `window.__timelines`.
- **Note**: The `video` element (line 67) uses an absolute `data-start="3"`. This is valid.
### compositions/intro-seq.html
**Status**: COMPLIANT
- Correctly uses `<template>` and `data-composition-id`.
- Dimensions and duration are explicitly set.
- Script is deterministic (uses fixed text and timing).
- Registers `intro-seq` timeline.
### compositions/grid-bg.html
**Status**: COMPLIANT
- Correctly uses `<template>` and `data-composition-id`.
- Dimensions and duration are explicitly set.
- Loops are finite and deterministic.
- Registers `grid-bg` timeline.
### compositions/captions.html
**Status**: HAS_ISSUES
- **Issue**: Line 98 uses an empty tween `tl.to({}, { duration: 2 })` to extend the timeline.
- **Fix**: Remove the empty tween. The duration is already correctly defined in the `data-duration="16.04"` attribute on the root `div` (line 2).
- **Issue**: The `data-duration` is set to `16.04`, but the last caption ends at `16.019`. This is fine as `data-duration` takes precedence.
### compositions/data-graphics.html
**Status**: COMPLIANT
- Correctly uses `<template>` and `data-composition-id`.
- Dimensions and duration are explicitly set.
- Counter animation is deterministic in its logic.
@@ -10,7 +10,7 @@
[data-composition-id="background"] {
width: 1920px;
height: 1080px;
background-color: #0A0A0A;
background-color: #0a0a0a;
overflow: hidden;
position: relative;
}
@@ -30,7 +30,7 @@
position: absolute;
top: 0;
left: 0;
background-image:
background-image:
linear-gradient(to right, rgba(138, 138, 138, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(138, 138, 138, 0.05) 1px, transparent 1px);
background-size: 60px 60px;
@@ -51,11 +51,7 @@
[data-composition-id="background"] .scan-line {
width: 100%;
height: 100px; /* Taller for a softer gradient feel */
background: linear-gradient(to bottom,
transparent,
rgba(26, 26, 26, 0.1) 50%,
transparent
);
background: linear-gradient(to bottom, transparent, rgba(26, 26, 26, 0.1) 50%, transparent);
position: absolute;
top: -100px;
left: 0;
@@ -65,44 +61,53 @@
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
const duration = 19.04;
// 1. Subtle Grid Drift
// Move 60px (one grid unit) horizontally over the full duration
tl.to('[data-composition-id="background"] .modular-grid', {
x: -60,
duration: duration,
ease: "none"
}, 0);
tl.to(
'[data-composition-id="background"] .modular-grid',
{
x: -60,
duration: duration,
ease: "none",
},
0,
);
// 2. Subtle Scan Line Texture
// Dark gray/blue (#1A1A1A) at 0.1 opacity
const scanCycle = 8; // Slower, more sophisticated movement
const cycles = Math.ceil(duration / scanCycle);
for (let i = 0; i < cycles; i++) {
tl.fromTo('[data-composition-id="background"] .scan-line',
{ y: -100 },
{
y: 1180,
duration: scanCycle,
ease: "none"
},
i * scanCycle
tl.fromTo(
'[data-composition-id="background"] .scan-line',
{ y: -100 },
{
y: 1180,
duration: scanCycle,
ease: "none",
},
i * scanCycle,
);
}
// 3. Ambient Opacity Pulse
// Very subtle breathing effect for the dot matrix
tl.to('[data-composition-id="background"] .dot-matrix', {
opacity: 0.6,
duration: duration / 2,
ease: "sine.inOut",
yoyo: true,
repeat: 1
}, 0);
tl.to(
'[data-composition-id="background"] .dot-matrix',
{
opacity: 0.6,
duration: duration / 2,
ease: "sine.inOut",
yoyo: true,
repeat: 1,
},
0,
);
window.__timelines["background"] = tl;
})();
@@ -3,7 +3,7 @@
<div id="captions-container"></div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@700&display=swap");
[data-composition-id="captions"] {
width: 1920px;
@@ -32,10 +32,10 @@
transform: translateX(-50%);
width: 90%; /* Prevent bleeding */
max-width: 1728px; /* 1920 * 0.9 */
font-family: 'Space Mono', monospace;
font-family: "Space Mono", monospace;
font-weight: 700;
font-size: 64px;
color: #33FF66;
color: #33ff66;
text-transform: uppercase;
text-align: center;
opacity: 0;
@@ -46,52 +46,109 @@
</style>
<script>
(function() {
const TRANSCRIPT = [{'text': 'We', 'start': 0.119, 'end': 0.259}, {'text': 'asked', 'start': 0.319, 'end': 0.479}, {'text': 'what', 'start': 0.519, 'end': 0.659}, {'text': 'you', 'start': 0.699, 'end': 0.819}, {'text': 'needed.', 'start': 0.859, 'end': 1.819}, {'text': 'Forty-seven', 'start': 1.839, 'end': 2.299}, {'text': 'percent', 'start': 2.399, 'end': 2.679}, {'text': 'of', 'start': 2.7, 'end': 2.799}, {'text': 'you', 'start': 2.839, 'end': 2.939}, {'text': 'said', 'start': 3.039, 'end': 3.179}, {'text': 'motion', 'start': 3.24, 'end': 3.559}, {'text': 'graphics.', 'start': 3.579, 'end': 4.639}, {'text': 'Sixty-two', 'start': 4.659, 'end': 5.179}, {'text': 'percent', 'start': 5.299, 'end': 5.759}, {'text': 'said', 'start': 5.859, 'end': 5.98}, {'text': 'static', 'start': 6.099, 'end': 6.399}, {'text': 'content', 'start': 6.46, 'end': 6.879}, {'text': 'was', 'start': 6.92, 'end': 7.079}, {'text': 'costing', 'start': 7.099, 'end': 7.48}, {'text': 'you', 'start': 7.5, 'end': 7.579}, {'text': 'attention,', 'start': 7.679, 'end': 8.659}, {'text': 'and', 'start': 8.699, 'end': 8.86}, {'text': 'three', 'start': 8.88, 'end': 9.06}, {'text': 'out', 'start': 9.079, 'end': 9.18}, {'text': 'of', 'start': 9.199, 'end': 9.34}, {'text': 'four', 'start': 9.38, 'end': 9.799}, {'text': 'said', 'start': 9.84, 'end': 10.0}, {'text': 'you', 'start': 10.019, 'end': 10.159}, {'text': 'know', 'start': 10.179, 'end': 10.36}, {'text': 'the', 'start': 10.38, 'end': 10.479}, {'text': 'look', 'start': 10.52, 'end': 10.699}, {'text': 'you', 'start': 10.739, 'end': 10.859}, {'text': 'want', 'start': 10.98, 'end': 11.34}, {'text': 'but', 'start': 11.359, 'end': 11.52}, {'text': "don't", 'start': 11.56, 'end': 11.779}, {'text': 'have', 'start': 11.819, 'end': 11.94}, {'text': 'the', 'start': 11.96, 'end': 12.06}, {'text': 'editing', 'start': 12.079, 'end': 12.4}, {'text': 'skills', 'start': 12.52, 'end': 12.86}, {'text': 'to', 'start': 12.88, 'end': 13.0}, {'text': 'get', 'start': 13.019, 'end': 13.18}, {'text': 'there.', 'start': 13.22, 'end': 14.22}, {'text': 'So', 'start': 14.239, 'end': 14.399}, {'text': 'we', 'start': 14.42, 'end': 14.52}, {'text': 'built', 'start': 14.619, 'end': 14.88}, {'text': 'Editor', 'start': 15.079, 'end': 15.42}, {'text': 'Agent', 'start': 15.619, 'end': 16.019}];
const container = document.getElementById('captions-container');
(function () {
const TRANSCRIPT = [
{ text: "We", start: 0.119, end: 0.259 },
{ text: "asked", start: 0.319, end: 0.479 },
{ text: "what", start: 0.519, end: 0.659 },
{ text: "you", start: 0.699, end: 0.819 },
{ text: "needed.", start: 0.859, end: 1.819 },
{ text: "Forty-seven", start: 1.839, end: 2.299 },
{ text: "percent", start: 2.399, end: 2.679 },
{ text: "of", start: 2.7, end: 2.799 },
{ text: "you", start: 2.839, end: 2.939 },
{ text: "said", start: 3.039, end: 3.179 },
{ text: "motion", start: 3.24, end: 3.559 },
{ text: "graphics.", start: 3.579, end: 4.639 },
{ text: "Sixty-two", start: 4.659, end: 5.179 },
{ text: "percent", start: 5.299, end: 5.759 },
{ text: "said", start: 5.859, end: 5.98 },
{ text: "static", start: 6.099, end: 6.399 },
{ text: "content", start: 6.46, end: 6.879 },
{ text: "was", start: 6.92, end: 7.079 },
{ text: "costing", start: 7.099, end: 7.48 },
{ text: "you", start: 7.5, end: 7.579 },
{ text: "attention,", start: 7.679, end: 8.659 },
{ text: "and", start: 8.699, end: 8.86 },
{ text: "three", start: 8.88, end: 9.06 },
{ text: "out", start: 9.079, end: 9.18 },
{ text: "of", start: 9.199, end: 9.34 },
{ text: "four", start: 9.38, end: 9.799 },
{ text: "said", start: 9.84, end: 10.0 },
{ text: "you", start: 10.019, end: 10.159 },
{ text: "know", start: 10.179, end: 10.36 },
{ text: "the", start: 10.38, end: 10.479 },
{ text: "look", start: 10.52, end: 10.699 },
{ text: "you", start: 10.739, end: 10.859 },
{ text: "want", start: 10.98, end: 11.34 },
{ text: "but", start: 11.359, end: 11.52 },
{ text: "don't", start: 11.56, end: 11.779 },
{ text: "have", start: 11.819, end: 11.94 },
{ text: "the", start: 11.96, end: 12.06 },
{ text: "editing", start: 12.079, end: 12.4 },
{ text: "skills", start: 12.52, end: 12.86 },
{ text: "to", start: 12.88, end: 13.0 },
{ text: "get", start: 13.019, end: 13.18 },
{ text: "there.", start: 13.22, end: 14.22 },
{ text: "So", start: 14.239, end: 14.399 },
{ text: "we", start: 14.42, end: 14.52 },
{ text: "built", start: 14.619, end: 14.88 },
{ text: "Editor", start: 15.079, end: 15.42 },
{ text: "Agent", start: 15.619, end: 16.019 },
];
const container = document.getElementById("captions-container");
const tl = gsap.timeline({ paused: true });
// Grouping logic based on the provided transcript and timing
// We use the transcript token directly to ensure deterministic behavior
// and sync with the actual audio.
const captionGroups = [
{ text: 'WE ASKED WHAT YOU NEEDED.', start: 0.119, end: 1.839 },
{ text: 'FORTY-SEVEN PERCENT OF YOU SAID', start: 1.839, end: 3.24 },
{ text: 'MOTION GRAPHICS.', start: 3.24, end: 4.659 },
{ text: 'SIXTY-TWO PERCENT SAID STATIC', start: 4.659, end: 6.46 },
{ text: 'CONTENT WAS COSTING YOU ATTENTION,', start: 6.46, end: 8.699 },
{ text: 'AND THREE OUT OF FOUR', start: 8.699, end: 9.84 },
{ text: 'SAID YOU KNOW THE LOOK', start: 9.84, end: 10.739 },
{ text: 'YOU WANT BUT DON\'T HAVE', start: 10.739, end: 11.96 },
{ text: 'THE EDITING SKILLS TO GET', start: 11.96, end: 13.22 },
{ text: 'THERE. SO WE BUILT EDITOR', start: 13.22, end: 15.619 },
{ text: 'AGENT', start: 15.619, end: 16.019 }
{ text: "WE ASKED WHAT YOU NEEDED.", start: 0.119, end: 1.839 },
{ text: "FORTY-SEVEN PERCENT OF YOU SAID", start: 1.839, end: 3.24 },
{ text: "MOTION GRAPHICS.", start: 3.24, end: 4.659 },
{ text: "SIXTY-TWO PERCENT SAID STATIC", start: 4.659, end: 6.46 },
{ text: "CONTENT WAS COSTING YOU ATTENTION,", start: 6.46, end: 8.699 },
{ text: "AND THREE OUT OF FOUR", start: 8.699, end: 9.84 },
{ text: "SAID YOU KNOW THE LOOK", start: 9.84, end: 10.739 },
{ text: "YOU WANT BUT DON'T HAVE", start: 10.739, end: 11.96 },
{ text: "THE EDITING SKILLS TO GET", start: 11.96, end: 13.22 },
{ text: "THERE. SO WE BUILT EDITOR", start: 13.22, end: 15.619 },
{ text: "AGENT", start: 15.619, end: 16.019 },
];
captionGroups.forEach((group, index) => {
const div = document.createElement('div');
div.className = 'caption-line';
const div = document.createElement("div");
div.className = "caption-line";
div.id = `caption-${index}`;
div.textContent = group.text;
container.appendChild(div);
// Show the caption
// Using a very short duration for a "cut" effect, or 0 for instant
tl.to(div, {
opacity: 1,
duration: 0.05,
ease: "none"
}, group.start);
tl.to(
div,
{
opacity: 1,
duration: 0.05,
ease: "none",
},
group.start,
);
// Hide the caption when the next one starts, or at its end if it's the last one
const hideTime = (index < captionGroups.length - 1) ? captionGroups[index + 1].start : group.end;
tl.to(div, {
opacity: 0,
duration: 0.05,
ease: "none"
}, hideTime);
const hideTime =
index < captionGroups.length - 1 ? captionGroups[index + 1].start : group.end;
tl.to(
div,
{
opacity: 0,
duration: 0.05,
ease: "none",
},
hideTime,
);
});
window.__timelines["captions"] = tl;
@@ -1,8 +1,13 @@
<template id="data-graphics-template">
<div data-composition-id="data-graphics" data-width="1920" data-height="1080" data-duration="16.04">
<div
data-composition-id="data-graphics"
data-width="1920"
data-height="1080"
data-duration="16.04"
>
<div class="grid-container">
<!-- Graphic 1: 47% NEED MOTION GRAPHICS -->
<div id="graphic-1" class="graphic-box" style="opacity: 0;">
<div id="graphic-1" class="graphic-box" style="opacity: 0">
<div class="label">47% NEED MOTION GRAPHICS</div>
<div class="progress-bar-container">
<div class="progress-bar-bg"></div>
@@ -12,7 +17,7 @@
</div>
<!-- Graphic 2: 62% LOSE ATTENTION -->
<div id="graphic-2" class="graphic-box" style="opacity: 0;">
<div id="graphic-2" class="graphic-box" style="opacity: 0">
<div class="label">62% LOSE ATTENTION</div>
<div class="chart-container">
<div class="bar-group">
@@ -28,7 +33,7 @@
</div>
<!-- Graphic 3: 75% LACK SKILLS -->
<div id="graphic-3" class="graphic-box" style="opacity: 0;">
<div id="graphic-3" class="graphic-box" style="opacity: 0">
<div class="label">75% LACK SKILLS</div>
<div class="radial-container">
<div class="radial-svg-wrapper">
@@ -43,13 +48,13 @@
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap");
[data-composition-id="data-graphics"] {
background: transparent;
font-family: 'Space Mono', monospace;
font-family: "Space Mono", monospace;
text-transform: uppercase;
color: #8A8A8A;
color: #8a8a8a;
overflow: hidden;
width: 1920px;
height: 1080px;
@@ -82,9 +87,17 @@
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
[data-composition-id="data-graphics"] #graphic-1 { grid-row: 2 / 5; margin-bottom: 10px; }
[data-composition-id="data-graphics"] #graphic-2 { grid-row: 5 / 8; margin-bottom: 10px; }
[data-composition-id="data-graphics"] #graphic-3 { grid-row: 8 / 11; }
[data-composition-id="data-graphics"] #graphic-1 {
grid-row: 2 / 5;
margin-bottom: 10px;
}
[data-composition-id="data-graphics"] #graphic-2 {
grid-row: 5 / 8;
margin-bottom: 10px;
}
[data-composition-id="data-graphics"] #graphic-3 {
grid-row: 8 / 11;
}
[data-composition-id="data-graphics"] .label {
font-size: 20px;
@@ -122,7 +135,13 @@
left: 0;
width: 100%;
height: 100%;
background: repeating-linear-gradient(90deg, rgba(138, 138, 138, 0.2), rgba(138, 138, 138, 0.2) 2px, transparent 2px, transparent 12px);
background: repeating-linear-gradient(
90deg,
rgba(138, 138, 138, 0.2),
rgba(138, 138, 138, 0.2) 2px,
transparent 2px,
transparent 12px
);
}
[data-composition-id="data-graphics"] .progress-bar-fill {
@@ -172,8 +191,12 @@
height: 0%;
}
[data-composition-id="data-graphics"] .bar-fill.green { background: rgba(51, 255, 102, 0.8); }
[data-composition-id="data-graphics"] .bar-fill.gray { background: rgba(138, 138, 138, 0.5); }
[data-composition-id="data-graphics"] .bar-fill.green {
background: rgba(51, 255, 102, 0.8);
}
[data-composition-id="data-graphics"] .bar-fill.gray {
background: rgba(138, 138, 138, 0.5);
}
/* Radial Styles */
[data-composition-id="data-graphics"] .radial-container {
@@ -212,14 +235,14 @@
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
const REVEAL_DURATION = 0.8;
const EASE = "power2.out";
const graphicSelectors = [
'[data-composition-id="data-graphics"] #graphic-1',
'[data-composition-id="data-graphics"] #graphic-2',
'[data-composition-id="data-graphics"] #graphic-3'
'[data-composition-id="data-graphics"] #graphic-3',
];
// Helper for counter animation
@@ -227,97 +250,141 @@
const obj = { val: 0 };
const el = document.querySelector(`[data-composition-id="data-graphics"] #${id}`);
if (!el) return;
tl.to(obj, {
val: target,
duration: duration,
ease: EASE,
onUpdate: () => {
el.innerText = Math.floor(obj.val).toString().padStart(2, '0') + '%';
}
}, startTime);
tl.to(
obj,
{
val: target,
duration: duration,
ease: EASE,
onUpdate: () => {
el.innerText = Math.floor(obj.val).toString().padStart(2, "0") + "%";
},
},
startTime,
);
}
gsap.set(graphicSelectors, { x: 24 });
// 1. 47% NEED MOTION GRAPHICS at 1.839s
tl.to('[data-composition-id="data-graphics"] #graphic-1', {
opacity: 1,
x: 0,
duration: 0.5,
ease: "power2.out"
}, 1.839);
tl.to('[data-composition-id="data-graphics"] #bar-1', {
width: '47%',
duration: REVEAL_DURATION,
ease: EASE
}, 1.839 + 0.2);
animateCounter('counter-1', 47, REVEAL_DURATION, 1.839 + 0.2);
tl.to(
'[data-composition-id="data-graphics"] #graphic-1',
{
opacity: 1,
x: 0,
duration: 0.5,
ease: "power2.out",
},
1.839,
);
tl.to(
'[data-composition-id="data-graphics"] #bar-1',
{
width: "47%",
duration: REVEAL_DURATION,
ease: EASE,
},
1.839 + 0.2,
);
animateCounter("counter-1", 47, REVEAL_DURATION, 1.839 + 0.2);
// 2. 62% LOSE ATTENTION at 4.659s
tl.to('[data-composition-id="data-graphics"] #graphic-2', {
opacity: 1,
duration: 0.5,
ease: "power2.out"
}, 4.659);
tl.to('[data-composition-id="data-graphics"] #bar-2-static', {
height: '30%',
duration: REVEAL_DURATION,
ease: EASE
}, 4.659 + 0.2);
tl.to('[data-composition-id="data-graphics"] #bar-2-motion', {
height: '62%',
duration: REVEAL_DURATION,
ease: EASE
}, 4.659 + 0.4);
animateCounter('counter-2', 62, REVEAL_DURATION, 4.659 + 0.4);
tl.to(
'[data-composition-id="data-graphics"] #graphic-2',
{
opacity: 1,
duration: 0.5,
ease: "power2.out",
},
4.659,
);
tl.to(
'[data-composition-id="data-graphics"] #bar-2-static',
{
height: "30%",
duration: REVEAL_DURATION,
ease: EASE,
},
4.659 + 0.2,
);
tl.to(
'[data-composition-id="data-graphics"] #bar-2-motion',
{
height: "62%",
duration: REVEAL_DURATION,
ease: EASE,
},
4.659 + 0.4,
);
animateCounter("counter-2", 62, REVEAL_DURATION, 4.659 + 0.4);
// 3. 75% LACK SKILLS at 8.88s
tl.to('[data-composition-id="data-graphics"] #graphic-3', {
opacity: 1,
duration: 0.5,
ease: "power2.out"
}, 8.88);
tl.to(
'[data-composition-id="data-graphics"] #graphic-3',
{
opacity: 1,
duration: 0.5,
ease: "power2.out",
},
8.88,
);
const circumference = 282.74;
const offset = circumference - (0.75 * circumference);
tl.to('[data-composition-id="data-graphics"] #radial-fill', {
strokeDashoffset: offset,
duration: REVEAL_DURATION,
ease: EASE
}, 8.88 + 0.2);
animateCounter('counter-3', 75, REVEAL_DURATION, 8.88 + 0.2);
const offset = circumference - 0.75 * circumference;
tl.to(
'[data-composition-id="data-graphics"] #radial-fill',
{
strokeDashoffset: offset,
duration: REVEAL_DURATION,
ease: EASE,
},
8.88 + 0.2,
);
animateCounter("counter-3", 75, REVEAL_DURATION, 8.88 + 0.2);
// A one-way settle keeps the UI alive without repeat/yoyo timeline noise.
const greenElements = [
'[data-composition-id="data-graphics"] .progress-bar-fill',
'[data-composition-id="data-graphics"] .bar-fill.green',
'[data-composition-id="data-graphics"] .radial-progress',
'[data-composition-id="data-graphics"] .counter'
'[data-composition-id="data-graphics"] .counter',
];
gsap.set(greenElements, { opacity: 0.85 });
tl.to(greenElements, {
opacity: 1,
duration: 1.2,
stagger: 0.08,
ease: "none"
}, 2);
tl.to(
greenElements,
{
opacity: 1,
duration: 1.2,
stagger: 0.08,
ease: "none",
},
2,
);
// Outro: Fade all out near the end
tl.to(['[data-composition-id="data-graphics"] #graphic-1', '[data-composition-id="data-graphics"] #graphic-2', '[data-composition-id="data-graphics"] #graphic-3'], {
opacity: 0,
y: 20,
duration: 0.5,
ease: "power2.in"
}, 15.5);
tl.to(
[
'[data-composition-id="data-graphics"] #graphic-1',
'[data-composition-id="data-graphics"] #graphic-2',
'[data-composition-id="data-graphics"] #graphic-3',
],
{
opacity: 0,
y: 20,
duration: 0.5,
ease: "power2.in",
},
15.5,
);
window.__timelines["data-graphics"] = tl;
})();
@@ -12,7 +12,7 @@
left: 0;
width: 1920px;
height: 1080px;
background-color: #0A0A0A;
background-color: #0a0a0a;
overflow: hidden;
}
@@ -23,7 +23,7 @@
width: 100%;
height: 100%;
/* 40px grid pattern using linear-gradients */
background-image:
background-image:
linear-gradient(to right, rgba(138, 138, 138, 0.1) 1px, transparent 1px),
linear-gradient(to bottom, rgba(138, 138, 138, 0.1) 1px, transparent 1px);
background-size: 40px 40px;
@@ -36,7 +36,7 @@
left: 0;
width: 100%;
height: 2px;
background-color: #33FF66;
background-color: #33ff66;
opacity: 0.2;
box-shadow: 0 0 10px 1px rgba(51, 255, 102, 0.3);
transform: translateY(-10px);
@@ -44,41 +44,57 @@
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
const totalDuration = 19.04;
const scanLineSelector = '[data-composition-id="grid-bg"] .scan-line';
const gridSelector = '[data-composition-id="grid-bg"] .grid-overlay';
const halfDuration = totalDuration / 2;
gsap.set(scanLineSelector, { y: -20 });
gsap.set(gridSelector, { scale: 1, opacity: 0.9 });
// 1. Single continuous scan keeps the texture moving without stacked fromTo loops.
tl.to(scanLineSelector, {
y: 1100,
duration: totalDuration,
ease: "none"
}, 0);
tl.to(
scanLineSelector,
{
y: 1100,
duration: totalDuration,
ease: "none",
},
0,
);
// 2. Gentle scale breathe across the full composition window.
tl.to(gridSelector, {
scale: 1.02,
duration: halfDuration,
ease: "sine.inOut"
}, 0);
tl.to(gridSelector, {
scale: 1,
duration: totalDuration - halfDuration,
ease: "sine.inOut"
}, halfDuration);
tl.to(
gridSelector,
{
scale: 1.02,
duration: halfDuration,
ease: "sine.inOut",
},
0,
);
tl.to(
gridSelector,
{
scale: 1,
duration: totalDuration - halfDuration,
ease: "sine.inOut",
},
halfDuration,
);
// 3. One-way opacity settle keeps the grid alive without repeat/yoyo artifacts.
tl.to(gridSelector, {
opacity: 1,
duration: 1.2,
ease: "none"
}, 0);
tl.to(
gridSelector,
{
opacity: 1,
duration: 1.2,
ease: "none",
},
0,
);
window.__timelines["grid-bg"] = tl;
})();
@@ -6,17 +6,17 @@
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap");
[data-composition-id="intro-seq"] {
background-color: #0A0A0A;
background-color: #0a0a0a;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Space Mono', monospace;
font-family: "Space Mono", monospace;
width: 100%;
height: 100%;
color: #33FF66;
color: #33ff66;
}
[data-composition-id="intro-seq"] .terminal-container {
@@ -33,7 +33,9 @@
text-transform: uppercase;
min-height: 1.2em;
white-space: pre;
text-shadow: 0 0 10px rgba(51, 255, 102, 0.6), 0 0 20px rgba(51, 255, 102, 0.2);
text-shadow:
0 0 10px rgba(51, 255, 102, 0.6),
0 0 20px rgba(51, 255, 102, 0.2);
display: flex;
align-items: center;
}
@@ -47,7 +49,7 @@
display: inline-block;
width: 0.6em;
height: 1.1em;
background-color: #33FF66;
background-color: #33ff66;
margin-left: 5px;
box-shadow: 0 0 10px rgba(51, 255, 102, 0.8);
opacity: 0;
@@ -55,63 +57,83 @@
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
const line1Text = 'SYSTEM BOOTING...';
const line2Text = 'EDITOR AGENT v1.0';
const line1Text = "SYSTEM BOOTING...";
const line2Text = "EDITOR AGENT v1.0";
const charDuration = 0.05;
const blinkDuration = 0.1;
const line1El = document.getElementById('line1');
const line2El = document.getElementById('line2');
const line1El = document.getElementById("line1");
const line2El = document.getElementById("line2");
function createLine(el, text, startTime) {
// Clear and create spans
el.innerHTML = '';
const chars = text.split('').map(char => {
const span = document.createElement('span');
span.className = 'char';
span.textContent = char === ' ' ? '\u00A0' : char;
el.innerHTML = "";
const chars = text.split("").map((char) => {
const span = document.createElement("span");
span.className = "char";
span.textContent = char === " " ? "\u00A0" : char;
el.appendChild(span);
return span;
});
const cursor = document.createElement('div');
cursor.className = 'cursor';
const cursor = document.createElement("div");
cursor.className = "cursor";
el.appendChild(cursor);
tl.set(cursor, {
display: 'inline-block',
opacity: 1
}, startTime);
tl.set(
cursor,
{
display: "inline-block",
opacity: 1,
},
startTime,
);
// Animate characters
chars.forEach((char, i) => {
const time = startTime + (i * charDuration);
tl.to(char, {
opacity: 1,
duration: 0.01,
ease: "none"
}, time);
const time = startTime + i * charDuration;
tl.to(
char,
{
opacity: 1,
duration: 0.01,
ease: "none",
},
time,
);
});
// Give the cursor a short deterministic settle instead of a repeat/yoyo blink.
const lineEndTime = startTime + (chars.length * charDuration);
tl.to(cursor, {
opacity: 0.25,
duration: blinkDuration,
ease: "none"
}, lineEndTime);
tl.to(cursor, {
opacity: 1,
duration: blinkDuration,
ease: "none"
}, lineEndTime + blinkDuration);
tl.set(cursor, {
opacity: 0
}, lineEndTime + (blinkDuration * 2));
const lineEndTime = startTime + chars.length * charDuration;
tl.to(
cursor,
{
opacity: 0.25,
duration: blinkDuration,
ease: "none",
},
lineEndTime,
);
tl.to(
cursor,
{
opacity: 1,
duration: blinkDuration,
ease: "none",
},
lineEndTime + blinkDuration,
);
tl.set(
cursor,
{
opacity: 0,
},
lineEndTime + blinkDuration * 2,
);
return lineEndTime + 0.5;
}
@@ -123,13 +145,18 @@
createLine(line2El, line2Text, line1End);
// A subtle one-way settle keeps the intro alive without repeat/yoyo seek edge cases.
tl.fromTo('[data-composition-id="intro-seq"] .terminal-container', {
opacity: 0.92
}, {
opacity: 1,
duration: 0.6,
ease: "none"
}, 0);
tl.fromTo(
'[data-composition-id="intro-seq"] .terminal-container',
{
opacity: 0.92,
},
{
opacity: 1,
duration: 0.6,
ease: "none",
},
0,
);
window.__timelines["intro-seq"] = tl;
})();
@@ -5,14 +5,14 @@
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap');
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap");
[data-composition-id="intro"] {
background: transparent;
display: flex;
justify-content: center;
align-items: center;
font-family: 'JetBrains Mono', monospace;
font-family: "JetBrains Mono", monospace;
}
[data-composition-id="intro"] .terminal-container {
@@ -24,7 +24,7 @@
}
[data-composition-id="intro"] .terminal-text {
color: #FFFFFF;
color: #ffffff;
font-size: 72px;
font-weight: 700;
text-transform: uppercase;
@@ -42,16 +42,16 @@
</style>
<script>
(function() {
(function () {
const text = "SYSTEM BOOT: EDITOR AGENT";
const container = document.getElementById('terminal-text');
const container = document.getElementById("terminal-text");
// Clear and create character spans for precise animation
container.innerHTML = '';
const chars = text.split('').map(char => {
const span = document.createElement('span');
span.className = 'char';
span.textContent = char === ' ' ? '\u00A0' : char; // Use non-breaking space for layout
container.innerHTML = "";
const chars = text.split("").map((char) => {
const span = document.createElement("span");
span.className = "char";
span.textContent = char === " " ? "\u00A0" : char; // Use non-breaking space for layout
container.appendChild(span);
return span;
});
@@ -61,24 +61,32 @@
// Character-by-character reveal
// Using stagger for a more modern, sophisticated implementation
tl.to(chars, {
opacity: 1,
duration: 0.01, // Instant reveal per character
stagger: {
each: charDuration,
ease: "none" // Linear sequence
}
}, 0);
tl.to(
chars,
{
opacity: 1,
duration: 0.01, // Instant reveal per character
stagger: {
each: charDuration,
ease: "none", // Linear sequence
},
},
0,
);
// Add a subtle "flicker" or "pulse" to the glow for sophistication
// This adds the "2-3 simultaneous motion sources" requirement
tl.to(container, {
textShadow: "0 0 25px rgba(51, 255, 102, 0.4)",
duration: 1.5,
ease: "sine.inOut",
yoyo: true,
repeat: 1 // Finite repeat to fit 3s duration
}, 0);
tl.to(
container,
{
textShadow: "0 0 25px rgba(51, 255, 102, 0.4)",
duration: 1.5,
ease: "sine.inOut",
yoyo: true,
repeat: 1, // Finite repeat to fit 3s duration
},
0,
);
// Register the timeline
window.__timelines["intro"] = tl;
@@ -1,12 +1,13 @@
<template id="main-template">
<div data-composition-id="main" data-width="1920" data-height="1080" data-duration="16.04">
<!-- A-roll Video -->
<video id="aroll"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/008477bc_391124feb22641f19fd463e5aeb1aeef.mp4"
data-start="0"
data-duration="16.04"
data-track-index="1">
</video>
<video
id="aroll"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/008477bc_391124feb22641f19fd463e5aeb1aeef.mp4"
data-start="0"
data-duration="16.04"
data-track-index="1"
></video>
<!-- Captions Container -->
<div id="captions-container"></div>
@@ -47,10 +48,10 @@
}
.caption-line {
font-family: 'JetBrains Mono', monospace;
font-family: "JetBrains Mono", monospace;
font-weight: bold;
font-size: 64px;
color: #FFFFFF;
color: #ffffff;
text-transform: uppercase;
text-align: center;
opacity: 0;
@@ -63,44 +64,104 @@
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
const TRANSCRIPT = [{'text': 'We', 'start': 0.119, 'end': 0.259}, {'text': 'asked', 'start': 0.319, 'end': 0.479}, {'text': 'what', 'start': 0.519, 'end': 0.659}, {'text': 'you', 'start': 0.699, 'end': 0.819}, {'text': 'needed.', 'start': 0.859, 'end': 1.819}, {'text': 'Forty-seven', 'start': 1.839, 'end': 2.299}, {'text': 'percent', 'start': 2.399, 'end': 2.679}, {'text': 'of', 'start': 2.7, 'end': 2.799}, {'text': 'you', 'start': 2.839, 'end': 2.939}, {'text': 'said', 'start': 3.039, 'end': 3.179}, {'text': 'motion', 'start': 3.24, 'end': 3.559}, {'text': 'graphics.', 'start': 3.579, 'end': 4.639}, {'text': 'Sixty-two', 'start': 4.659, 'end': 5.179}, {'text': 'percent', 'start': 5.299, 'end': 5.759}, {'text': 'said', 'start': 5.859, 'end': 5.98}, {'text': 'static', 'start': 6.099, 'end': 6.399}, {'text': 'content', 'start': 6.46, 'end': 6.879}, {'text': 'was', 'start': 6.92, 'end': 7.079}, {'text': 'costing', 'start': 7.099, 'end': 7.48}, {'text': 'you', 'start': 7.5, 'end': 7.579}, {'text': 'attention,', 'start': 7.679, 'end': 8.659}, {'text': 'and', 'start': 8.699, 'end': 8.86}, {'text': 'three', 'start': 8.88, 'end': 9.06}, {'text': 'out', 'start': 9.079, 'end': 9.18}, {'text': 'of', 'start': 9.199, 'end': 9.34}, {'text': 'four', 'start': 9.38, 'end': 9.799}, {'text': 'said', 'start': 9.84, 'end': 10.0}, {'text': 'you', 'start': 10.019, 'end': 10.159}, {'text': 'know', 'start': 10.179, 'end': 10.36}, {'text': 'the', 'start': 10.38, 'end': 10.479}, {'text': 'look', 'start': 10.52, 'end': 10.699}, {'text': 'you', 'start': 10.739, 'end': 10.859}, {'text': 'want', 'start': 10.98, 'end': 11.34}, {'text': 'but', 'start': 11.359, 'end': 11.52}, {'text': "don't", 'start': 11.56, 'end': 11.779}, {'text': 'have', 'start': 11.819, 'end': 11.94}, {'text': 'the', 'start': 11.96, 'end': 12.06}, {'text': 'editing', 'start': 12.079, 'end': 12.4}, {'text': 'skills', 'start': 12.52, 'end': 12.86}, {'text': 'to', 'start': 12.88, 'end': 13.0}, {'text': 'get', 'start': 13.019, 'end': 13.18}, {'text': 'there.', 'start': 13.22, 'end': 14.22}, {'text': 'So', 'start': 14.239, 'end': 14.399}, {'text': 'we', 'start': 14.42, 'end': 14.52}, {'text': 'built', 'start': 14.619, 'end': 14.88}, {'text': 'Editor', 'start': 15.079, 'end': 15.42}, {'text': 'Agent', 'start': 15.619, 'end': 16.019}];
const TRANSCRIPT = [
{ text: "We", start: 0.119, end: 0.259 },
{ text: "asked", start: 0.319, end: 0.479 },
{ text: "what", start: 0.519, end: 0.659 },
{ text: "you", start: 0.699, end: 0.819 },
{ text: "needed.", start: 0.859, end: 1.819 },
{ text: "Forty-seven", start: 1.839, end: 2.299 },
{ text: "percent", start: 2.399, end: 2.679 },
{ text: "of", start: 2.7, end: 2.799 },
{ text: "you", start: 2.839, end: 2.939 },
{ text: "said", start: 3.039, end: 3.179 },
{ text: "motion", start: 3.24, end: 3.559 },
{ text: "graphics.", start: 3.579, end: 4.639 },
{ text: "Sixty-two", start: 4.659, end: 5.179 },
{ text: "percent", start: 5.299, end: 5.759 },
{ text: "said", start: 5.859, end: 5.98 },
{ text: "static", start: 6.099, end: 6.399 },
{ text: "content", start: 6.46, end: 6.879 },
{ text: "was", start: 6.92, end: 7.079 },
{ text: "costing", start: 7.099, end: 7.48 },
{ text: "you", start: 7.5, end: 7.579 },
{ text: "attention,", start: 7.679, end: 8.659 },
{ text: "and", start: 8.699, end: 8.86 },
{ text: "three", start: 8.88, end: 9.06 },
{ text: "out", start: 9.079, end: 9.18 },
{ text: "of", start: 9.199, end: 9.34 },
{ text: "four", start: 9.38, end: 9.799 },
{ text: "said", start: 9.84, end: 10.0 },
{ text: "you", start: 10.019, end: 10.159 },
{ text: "know", start: 10.179, end: 10.36 },
{ text: "the", start: 10.38, end: 10.479 },
{ text: "look", start: 10.52, end: 10.699 },
{ text: "you", start: 10.739, end: 10.859 },
{ text: "want", start: 10.98, end: 11.34 },
{ text: "but", start: 11.359, end: 11.52 },
{ text: "don't", start: 11.56, end: 11.779 },
{ text: "have", start: 11.819, end: 11.94 },
{ text: "the", start: 11.96, end: 12.06 },
{ text: "editing", start: 12.079, end: 12.4 },
{ text: "skills", start: 12.52, end: 12.86 },
{ text: "to", start: 12.88, end: 13.0 },
{ text: "get", start: 13.019, end: 13.18 },
{ text: "there.", start: 13.22, end: 14.22 },
{ text: "So", start: 14.239, end: 14.399 },
{ text: "we", start: 14.42, end: 14.52 },
{ text: "built", start: 14.619, end: 14.88 },
{ text: "Editor", start: 15.079, end: 15.42 },
{ text: "Agent", start: 15.619, end: 16.019 },
];
const TRANSITION_DURATION = 0.25; // 250ms
const container = document.querySelector('[data-composition-id="main"]');
const aroll = container.querySelector('#aroll');
const captionsContainer = container.querySelector('#captions-container');
const aroll = container.querySelector("#aroll");
const captionsContainer = container.querySelector("#captions-container");
// --- A-roll Choreography ---
// 1. At 0.0s (relative to composition start at 3s): A-roll enters with a clean left-to-right wipe reveal.
tl.to(aroll, {
clipPath: 'inset(0 0% 0 0)',
duration: TRANSITION_DURATION,
ease: 'none'
}, 0.0);
tl.to(
aroll,
{
clipPath: "inset(0 0% 0 0)",
duration: TRANSITION_DURATION,
ease: "none",
},
0.0,
);
// 2. At 1.839s (relative to composition start at 3s): A-roll scales down and shifts.
// (4.839 - 3 = 1.839)
tl.to(aroll, {
scale: 0.6,
x: -384,
duration: TRANSITION_DURATION,
ease: 'none'
}, 1.839);
tl.to(
aroll,
{
scale: 0.6,
x: -384,
duration: TRANSITION_DURATION,
ease: "none",
},
1.839,
);
// 3. At 14.239s (relative to composition start at 3s): A-roll scales back up and recenters.
// (17.239 - 3 = 14.239)
tl.to(aroll, {
scale: 1,
x: 0,
duration: TRANSITION_DURATION,
ease: 'none'
}, 14.239);
tl.to(
aroll,
{
scale: 1,
x: 0,
duration: TRANSITION_DURATION,
ease: "none",
},
14.239,
);
// --- Captions Logic ---
function groupTranscript(transcript, maxWords) {
const groups = [];
for (let i = 0; i < transcript.length; i += maxWords) {
@@ -114,23 +175,26 @@
captionGroups.forEach((group, index) => {
const startTime = group[0].start;
// Stay visible until the next group replaces them.
const endTime = (index < captionGroups.length - 1)
? captionGroups[index + 1][0].start
: 16.04; // End of composition
const endTime =
index < captionGroups.length - 1 ? captionGroups[index + 1][0].start : 16.04; // End of composition
const text = group.map(w => w.text).join(' ');
const el = document.createElement('div');
el.className = 'caption-line';
const text = group.map((w) => w.text).join(" ");
const el = document.createElement("div");
el.className = "caption-line";
el.innerText = text;
captionsContainer.appendChild(el);
// Caption transitions: slight vertical shift
tl.to(el, {
opacity: 1,
y: 0,
duration: 0.2,
ease: 'none'
}, startTime);
tl.to(
el,
{
opacity: 1,
y: 0,
duration: 0.2,
ease: "none",
},
startTime,
);
// Hide caption when next one starts
if (index < captionGroups.length - 1) {
@@ -142,4 +206,4 @@
})();
</script>
</div>
</template>
</template>
@@ -39,12 +39,12 @@
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;800&display=swap');
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;800&display=swap");
[data-composition-id="stats"] {
font-family: 'JetBrains Mono', monospace;
font-family: "JetBrains Mono", monospace;
text-transform: uppercase;
color: #FFFFFF;
color: #ffffff;
background: transparent;
width: 1920px;
height: 1080px;
@@ -82,14 +82,14 @@
[data-composition-id="stats"] .stat-value {
font-size: 80px;
font-weight: 800;
color: #FFFFFF;
color: #ffffff;
line-height: 1;
}
[data-composition-id="stats"] .stat-label {
font-size: 32px;
font-weight: 400;
color: #8A8A8A;
color: #8a8a8a;
letter-spacing: 1px;
max-width: 500px;
}
@@ -98,13 +98,13 @@
[data-composition-id="stats"] .progress-bar-container {
width: 600px;
height: 12px;
background: #0A0A0A;
background: #0a0a0a;
}
[data-composition-id="stats"] .progress-bar-fill {
width: 0%;
height: 100%;
background: #FFFFFF;
background: #ffffff;
}
/* Stat 2: Circular Ring */
@@ -119,13 +119,13 @@
[data-composition-id="stats"] .ring-bg {
fill: none;
stroke: #0A0A0A;
stroke: #0a0a0a;
stroke-width: 24;
}
[data-composition-id="stats"] .ring-fill {
fill: none;
stroke: #FFFFFF;
stroke: #ffffff;
stroke-width: 24;
stroke-dasharray: 502.65; /* 2 * PI * 80 */
stroke-dashoffset: 502.65;
@@ -136,27 +136,27 @@
[data-composition-id="stats"] .bar-chart-container {
width: 60px;
height: 240px;
background: #0A0A0A;
background: #0a0a0a;
position: relative;
}
[data-composition-id="stats"] .bar-chart-fill {
width: 100%;
height: 0%;
background: #FFFFFF;
background: #ffffff;
position: absolute;
bottom: 0;
}
</style>
<script>
(function() {
(function () {
const tl = gsap.timeline({ paused: true });
// Timestamps (relative to composition start at 3s)
const T1 = 1.839; // 4.839 - 3
const T2 = 4.659; // 7.659 - 3
const T3 = 8.88; // 11.88 - 3
const T3 = 8.88; // 11.88 - 3
const END = 16.04; // 19.04 - 3
const FADE_DUR = 0.2;
const SLIDE_DUR = 0.3;
@@ -165,51 +165,59 @@
// Helper for number animation
function animateNumber(id, targetVal, startTime) {
const obj = { val: 0 };
tl.to(obj, {
val: targetVal,
duration: FILL_DUR,
ease: 'none',
onUpdate: function() {
const valEl = document.getElementById(id);
if (valEl) valEl.innerText = Math.floor(obj.val) + '%';
}
}, startTime);
tl.to(
obj,
{
val: targetVal,
duration: FILL_DUR,
ease: "none",
onUpdate: function () {
const valEl = document.getElementById(id);
if (valEl) valEl.innerText = Math.floor(obj.val) + "%";
},
},
startTime,
);
}
// Stat 1 Animation
tl.to('#stat-1', { opacity: 1, duration: FADE_DUR, ease: 'none' }, T1);
tl.to('#stat-1', { x: 0, duration: SLIDE_DUR, ease: 'none' }, T1);
tl.to('#fill-1', { width: '47%', duration: FILL_DUR, ease: 'none' }, T1);
animateNumber('val-1', 47, T1);
tl.to("#stat-1", { opacity: 1, duration: FADE_DUR, ease: "none" }, T1);
tl.to("#stat-1", { x: 0, duration: SLIDE_DUR, ease: "none" }, T1);
tl.to("#fill-1", { width: "47%", duration: FILL_DUR, ease: "none" }, T1);
animateNumber("val-1", 47, T1);
// Hide Stat 1 when Stat 2 starts
tl.to('#stat-1', { opacity: 0, x: -20, duration: FADE_DUR, ease: 'none' }, T2);
tl.to("#stat-1", { opacity: 0, x: -20, duration: FADE_DUR, ease: "none" }, T2);
// Stat 2 Animation
const ringCircumference = 2 * Math.PI * 80;
tl.to('#stat-2', { opacity: 1, duration: FADE_DUR, ease: 'none' }, T2);
tl.to('#stat-2', { x: 0, duration: SLIDE_DUR, ease: 'none' }, T2);
tl.to('#fill-2', {
strokeDashoffset: ringCircumference * (1 - 0.62),
duration: FILL_DUR,
ease: 'none'
}, T2);
animateNumber('val-2', 62, T2);
tl.to("#stat-2", { opacity: 1, duration: FADE_DUR, ease: "none" }, T2);
tl.to("#stat-2", { x: 0, duration: SLIDE_DUR, ease: "none" }, T2);
tl.to(
"#fill-2",
{
strokeDashoffset: ringCircumference * (1 - 0.62),
duration: FILL_DUR,
ease: "none",
},
T2,
);
animateNumber("val-2", 62, T2);
// Hide Stat 2 when Stat 3 starts
tl.to('#stat-2', { opacity: 0, x: -20, duration: FADE_DUR, ease: 'none' }, T3);
tl.to("#stat-2", { opacity: 0, x: -20, duration: FADE_DUR, ease: "none" }, T3);
// Stat 3 Animation
tl.to('#stat-3', { opacity: 1, duration: FADE_DUR, ease: 'none' }, T3);
tl.to('#stat-3', { x: 0, duration: SLIDE_DUR, ease: 'none' }, T3);
tl.to('#fill-3', { height: '75%', duration: FILL_DUR, ease: 'none' }, T3);
animateNumber('val-3', 75, T3);
tl.to("#stat-3", { opacity: 1, duration: FADE_DUR, ease: "none" }, T3);
tl.to("#stat-3", { x: 0, duration: SLIDE_DUR, ease: "none" }, T3);
tl.to("#fill-3", { height: "75%", duration: FILL_DUR, ease: "none" }, T3);
animateNumber("val-3", 75, T3);
// Hide Stat 3 at the end
tl.to('#stat-3', { opacity: 0, x: -20, duration: FADE_DUR, ease: 'none' }, END - 0.5);
tl.to("#stat-3", { opacity: 0, x: -20, duration: FADE_DUR, ease: "none" }, END - 0.5);
window.__timelines["stats"] = tl;
})();
</script>
</div>
</template>
</template>
@@ -1,6 +1,7 @@
# HyperFrames Design Review
## First Impression
This looks like a "hacker" template from a 2014 stock asset site. Its trying so hard to be "digital" and "techy" that it forgets to be actually sophisticated. The neon green on black is the most tired trope in the industry.
---
@@ -8,18 +9,21 @@ This looks like a "hacker" template from a 2014 stock asset site. Its trying
## CRITICAL Design Failures
### The "Matrix" Color Palette
**Where:** Global (`#33FF66` on `#0A0A0A`)
**What's wrong:** Using pure neon green on a near-black background is lazy. Its high-contrast in the worst way—it causes eye strain and looks like a parody of a terminal.
**Why it matters:** It lacks professional polish. Real high-end tech brands (like Stripe, Vercel, or even Apples developer materials) use more nuanced greens, subtle gradients, and varied opacities to create depth. This is just flat and aggressive.
**Fix it:** Shift the green to a more sophisticated mint or emerald (e.g., `#00FF9F` or `#4ADE80`). Introduce a secondary accent color like a deep slate blue or a muted purple to break the monotony.
### Typography Hierarchy is Non-Existent
**Where:** `compositions/captions.html` and `compositions/data-graphics.html`
**What's wrong:** Everything is screaming. The captions are 64px, the terminal lines are 72px, and the data counters are 48px. All of them are in 'Space Mono' or 'Courier New'.
**What's wrong:** Everything is screaming. The captions are 64px, the terminal lines are 72px, and the data counters are 48px. All of them are in 'Space Mono' or 'Courier New'.
**Why it matters:** When everything is bold, uppercase, and huge, nothing is important. The viewer doesn't know where to look. It feels like being shouted at by a calculator.
**Fix it:** Use a variable font. Mix weights. Make the labels in the data graphics much smaller (12-14px) and lighter. Use a clean sans-serif (like Inter or Geist) for the main captions and keep the mono font for the "data" elements only.
### The "Floating Box" Syndrome
**Where:** `compositions/data-graphics.html`
**What's wrong:** The data boxes are just floating on the right with a generic `backdrop-filter: blur(10px)`. They have no relationship to the grid behind them or the video next to them.
**Why it matters:** It looks like a UI overlay from a cheap mobile game. Theres no sense of "physicality" or integrated design.
@@ -30,11 +34,13 @@ This looks like a "hacker" template from a 2014 stock asset site. Its trying
## Design Improvements
### Robotic Motion
**Where:** `index.html` (A-roll transitions)
**The problem:** The scale and position shifts of the video (`scale: 0.6`, `x: -400`) are basic linear-feeling movements. The "wipe" reveal is a standard `clip-path` that feels like a default PowerPoint transition.
**Make it better:** Use more aggressive easing (e.g., `expo.out` or `custom-elastic`). Add a slight "overshoot" to the scaling. When the video moves, maybe it should have a slight tilt or a "glitch" chromatic aberration effect during the transition to match the "system" theme.
### Static Grid Background
**Where:** `compositions/grid-bg.html`
**The problem:** A 40px grid is fine, but its just... there. The "scan line" is a single 2px bar moving top to bottom. Its predictable and boring.
**Make it better:** Add "data noise"—tiny flickering pixels, coordinate numbers that change in the corners of the grid, or subtle "interference" patterns. Make the grid lines vary in opacity so it doesn't look like a math notebook.
@@ -44,7 +50,8 @@ This looks like a "hacker" template from a 2014 stock asset site. Its trying
## What Actually Works
### The Terminal Boot Sequence
The `intro-seq.html` actually has a decent rhythm. The character-by-character reveal with the slight "blink" on the current character is a nice touch. Its the only part of the composition that feels like it had some thought put into the *feeling* of the animation rather than just the mechanics.
The `intro-seq.html` actually has a decent rhythm. The character-by-character reveal with the slight "blink" on the current character is a nice touch. Its the only part of the composition that feels like it had some thought put into the _feeling_ of the animation rather than just the mechanics.
---
@@ -1,126 +1,148 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Digital Grid — Wim Crouwel Style</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<style>
body, html {
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
background-color: #0A0A0A;
overflow: hidden;
font-family: 'Courier New', Courier, monospace;
}
body,
html {
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
background-color: #0a0a0a;
overflow: hidden;
font-family: "Courier New", Courier, monospace;
}
#master-composition {
position: relative;
width: 1920px;
height: 1080px;
}
#master-composition {
position: relative;
width: 1920px;
height: 1080px;
}
#aroll-video {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1920px;
height: 1080px;
border-radius: 4px;
object-fit: cover;
clip-path: inset(0 100% 0 0); /* For left-to-right wipe */
}
#aroll-video {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1920px;
height: 1080px;
border-radius: 4px;
object-fit: cover;
clip-path: inset(0 100% 0 0); /* For left-to-right wipe */
}
</style>
</head>
<body>
<div id="master-composition" data-composition-id="master" data-width="1920" data-height="1080" data-duration="19.04">
<!-- Persistent Grid Background -->
<div id="grid-bg-comp"
data-composition-id="grid-bg"
data-composition-src="compositions/grid-bg.html"
data-start="0"
data-track-index="0">
</div>
</head>
<body>
<div
id="master-composition"
data-composition-id="master"
data-width="1920"
data-height="1080"
data-duration="19.04"
>
<!-- Persistent Grid Background -->
<div
id="grid-bg-comp"
data-composition-id="grid-bg"
data-composition-src="compositions/grid-bg.html"
data-start="0"
data-track-index="0"
></div>
<!-- Intro Sequence (0-3s) -->
<div id="intro-seq-comp"
data-composition-id="intro-seq"
data-composition-src="compositions/intro-seq.html"
data-start="0"
data-track-index="1"
data-duration="3">
</div>
<!-- Intro Sequence (0-3s) -->
<div
id="intro-seq-comp"
data-composition-id="intro-seq"
data-composition-src="compositions/intro-seq.html"
data-start="0"
data-track-index="1"
data-duration="3"
></div>
<!-- Data Graphics Layer -->
<div id="data-graphics-comp"
data-composition-id="data-graphics"
data-composition-src="compositions/data-graphics.html"
data-start="3"
data-track-index="2">
</div>
<!-- Data Graphics Layer -->
<div
id="data-graphics-comp"
data-composition-id="data-graphics"
data-composition-src="compositions/data-graphics.html"
data-start="3"
data-track-index="2"
></div>
<!-- A-roll Video (Starts after intro) -->
<video id="aroll-video"
data-start="3"
data-duration="16.04"
data-track-index="3"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/3053bdbd_3b610ff665ee4d11858cba59dbf47b8c.mp4">
</video>
<!-- Captions Layer -->
<div id="captions-comp"
data-composition-id="captions"
data-composition-src="compositions/captions.html"
data-start="3"
data-track-index="4">
</div>
<!-- A-roll Video (Starts after intro) -->
<video
id="aroll-video"
data-start="3"
data-duration="16.04"
data-track-index="3"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/3053bdbd_3b610ff665ee4d11858cba59dbf47b8c.mp4"
></video>
<!-- Captions Layer -->
<div
id="captions-comp"
data-composition-id="captions"
data-composition-src="compositions/captions.html"
data-start="3"
data-track-index="4"
></div>
</div>
<script>
(function() {
const tl = gsap.timeline({ paused: true });
const video = document.getElementById('aroll-video');
(function () {
const tl = gsap.timeline({ paused: true });
const video = document.getElementById("aroll-video");
// 1. A-roll entrance: Clean left-to-right wipe reveal at 3s
tl.to(video, {
clipPath: 'inset(0 0% 0 0)',
duration: 0.5,
ease: "power2.inOut"
}, 3);
// 1. A-roll entrance: Clean left-to-right wipe reveal at 3s
tl.to(
video,
{
clipPath: "inset(0 0% 0 0)",
duration: 0.5,
ease: "power2.inOut",
},
3,
);
// 2. First stat (47%) at 3 + 1.839 = 4.839s
// Trigger: A-roll scales down and shifts to left
tl.to(video, {
scale: 0.6,
x: -400,
duration: 0.4,
ease: "power2.inOut"
}, 4.839);
// 2. First stat (47%) at 3 + 1.839 = 4.839s
// Trigger: A-roll scales down and shifts to left
tl.to(
video,
{
scale: 0.6,
x: -400,
duration: 0.4,
ease: "power2.inOut",
},
4.839,
);
// 3. Second stat (62%) at 3 + 4.659 = 7.659s
// Stays scaled down
// 3. Second stat (62%) at 3 + 4.659 = 7.659s
// Stays scaled down
// 4. Next major narrative beat (75% lack skills) at 3 + 13.22 = 16.22s
// Trigger: A-roll scales back up to 100% and recenters after "editing skills to get there"
tl.to(video, {
scale: 1,
x: 0,
duration: 0.4,
ease: "power2.inOut"
}, 16.22);
// 4. Next major narrative beat (75% lack skills) at 3 + 13.22 = 16.22s
// Trigger: A-roll scales back up to 100% and recenters after "editing skills to get there"
tl.to(
video,
{
scale: 1,
x: 0,
duration: 0.4,
ease: "power2.inOut",
},
16.22,
);
// 5. Final beat (Editor Agent) at 3 + 14.22 = 17.22s
// Scale down again for final reveal? Or stay centered.
// Prompt says: "When the next major narrative beat begins: A-roll scales back up to 100% and recenters."
// "A-roll remains visible and on screen through the end of the video."
window.__timelines["master"] = tl;
})();
// 5. Final beat (Editor Agent) at 3 + 14.22 = 17.22s
// Scale down again for final reveal? Or stay centered.
// Prompt says: "When the next major narrative beat begins: A-roll scales back up to 100% and recenters."
// "A-roll remains visible and on screen through the end of the video."
window.__timelines["master"] = tl;
})();
</script>
</body>
</body>
</html>