mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-12 23:29:50 +00:00
initial code (#2)
* feat: initial code port from hyperframes-internal Port all OSS-ready packages from the internal monorepo: - @hyperframes/core — shared types, HTML generation, GSAP utilities, runtime - @hyperframes/cli — CLI for creating, previewing, and rendering compositions - @hyperframes/engine — framework-agnostic rendering engine (BeginFrame + FFmpeg) - @hyperframes/producer — video rendering pipeline (Puppeteer + FFmpeg) - @hyperframes/ui-player — browser-based video player component - @hyperframes/studio — composition editor (React frontend + Hono backend) Includes regression test suite with Docker-based test harness. All HeyGen-internal references, deployment infrastructure, and proprietary assets have been removed. Package names migrated from @app/* to @hyperframes/*. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: scrub internal codenames and stale references from OSS port - Replace static.heygen.ai runtime URLs in test fixtures - Remove internal CDN publish script (publish-hyperframe-runtime.ts) - Replace sandbox-studio, sandbox-interceptor, __magicEditRuntime with neutral names (studio, hyperframe-runtime, __hyperframeRuntime) - Fix stale Vault API / localhost references in docs - Remove broken deprecated_studio link Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove remaining internal codenames and stale references - Delete stale producer README.md and PIPELINE.md (referenced nonexistent files) - Replace "Cerberus" codename with "HyperFrames" in test design reviews - Replace magic-edit postMessage identifiers with hf-preview/hf-parent - Rename debug-magic-edit-timeline.ts to debug-timeline.ts - Replace "Motion Cut" with "HyperFrames" in Timeline comments - Fix studio/CLI references to nonexistent archive package (use local data/projects/ dir, stub render proxy) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
10621e7903
commit
9f8e5ba5a1
@@ -0,0 +1,62 @@
|
||||
# HyperFrame Schema Compliance Review
|
||||
|
||||
## Executive Summary
|
||||
- Total files reviewed: 6
|
||||
- Critical issues: 0
|
||||
- Overall compliance status: PASS
|
||||
|
||||
## Critical Issues
|
||||
None found.
|
||||
|
||||
## 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`
|
||||
- [x] No use of `Math.random()`, `Date.now()`, or non-deterministic code
|
||||
- [x] Primitive clips have required data attributes (`id`, `data-start`, `data-track`)
|
||||
- [x] `data-duration` specified for all `<img>` clips
|
||||
- [x] No manual media playback control (`video.play()`, `audio.pause()`, etc.)
|
||||
- [x] No manual clip mounting/unmounting in scripts
|
||||
- [x] Relative timing references are valid (no circular refs, referenced clips have known duration)
|
||||
- [x] Clips on same track don't overlap in time
|
||||
- [x] Reusable compositions in separate HTML files
|
||||
- [x] Composition files use `<template>` tags
|
||||
- [x] External compositions loaded via `data-composition-src`
|
||||
- [x] All script-animated content wrapped in compositions
|
||||
- [x] No infinite or zero-duration timelines
|
||||
|
||||
### index.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
|
||||
### compositions/background.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
|
||||
### compositions/captions.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
|
||||
### compositions/aroll.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
|
||||
### compositions/mograph_overlays.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
|
||||
### compositions/hero_moment.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Issues Found**:
|
||||
- None.
|
||||
@@ -0,0 +1,188 @@
|
||||
<template id="aroll-template">
|
||||
<div data-composition-id="aroll" data-width="1080" data-height="1920" data-duration="14">
|
||||
<!-- Grid Background -->
|
||||
<div class="grid-container">
|
||||
<div class="grid-line horizontal" style="top: 20%;"></div>
|
||||
<div class="grid-line horizontal" style="top: 40%;"></div>
|
||||
<div class="grid-line horizontal" style="top: 60%;"></div>
|
||||
<div class="grid-line horizontal" style="top: 80%;"></div>
|
||||
<div class="grid-line vertical" style="left: 20%;"></div>
|
||||
<div class="grid-line vertical" style="left: 40%;"></div>
|
||||
<div class="grid-line vertical" style="left: 60%;"></div>
|
||||
<div class="grid-line vertical" style="left: 80%;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Video Container -->
|
||||
<div class="video-frame">
|
||||
<div class="video-mask">
|
||||
<video
|
||||
id="aroll-video"
|
||||
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/c10d5d5c_5e0cbe68f361481f8d2c77b028e84504.mp4"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="0"
|
||||
crossorigin="anonymous"
|
||||
></video>
|
||||
</div>
|
||||
<!-- Diagonal Rule Lines for Wipe Effect -->
|
||||
<div class="wipe-line wipe-line-1"></div>
|
||||
<div class="wipe-line wipe-line-2"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="aroll"] {
|
||||
background: transparent;
|
||||
font-family: sans-serif;
|
||||
overflow: hidden;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Grid Styling */
|
||||
[data-composition-id="aroll"] .grid-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .grid-line {
|
||||
position: absolute;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .grid-line.horizontal {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .grid-line.vertical {
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Video Frame Styling */
|
||||
[data-composition-id="aroll"] .video-frame {
|
||||
position: absolute;
|
||||
/* Full width and height for impact, no padding */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #111;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
transform: translate(0, 0) scale(1);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .video-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover; /* Crop portrait video into the frame */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Wipe Lines */
|
||||
[data-composition-id="aroll"] .wipe-line {
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
width: 200%;
|
||||
height: 4px;
|
||||
left: -50%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .wipe-line-1 {
|
||||
top: 0;
|
||||
transform: rotate(45deg) translateY(-500px);
|
||||
}
|
||||
|
||||
[data-composition-id="aroll"] .wipe-line-2 {
|
||||
bottom: 0;
|
||||
transform: rotate(45deg) translateY(500px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
const frame = document.querySelector('[data-composition-id="aroll"] .video-frame');
|
||||
const wipe1 = document.querySelector('[data-composition-id="aroll"] .wipe-line-1');
|
||||
const wipe2 = document.querySelector('[data-composition-id="aroll"] .wipe-line-2');
|
||||
|
||||
// 1. Entrance & Scale Down (0s - 1.5s)
|
||||
// Starts full screen and scales down slightly to establish the grid
|
||||
tl.fromTo(frame, {
|
||||
scale: 1.1,
|
||||
opacity: 0
|
||||
}, {
|
||||
scale: 1.0, /* Fixed scale to fill frame better */
|
||||
opacity: 1,
|
||||
duration: 1.5,
|
||||
ease: 'power4.out'
|
||||
}, 0);
|
||||
|
||||
tl.fromTo(wipe1,
|
||||
{ opacity: 1, y: -500 },
|
||||
{ y: 1500, duration: 1.5, ease: 'power2.inOut' },
|
||||
0
|
||||
);
|
||||
tl.fromTo(wipe2,
|
||||
{ opacity: 1, y: 500 },
|
||||
{ y: -1500, duration: 1.5, ease: 'power2.inOut' },
|
||||
0.1
|
||||
);
|
||||
tl.to([wipe1, wipe2], { opacity: 0, duration: 0.3 }, 1.5);
|
||||
|
||||
// 2. Ambient Motion (1.5s - 7.319s)
|
||||
tl.to(frame, {
|
||||
y: '+=15',
|
||||
duration: 3,
|
||||
ease: 'sine.inOut',
|
||||
repeat: 1,
|
||||
yoyo: true
|
||||
}, 1.5);
|
||||
|
||||
// 3. Hero Moment Exit (7.319s - 8.319s)
|
||||
tl.to(frame, {
|
||||
scale: 0,
|
||||
opacity: 0,
|
||||
duration: 0.8,
|
||||
ease: 'power3.in'
|
||||
}, 7.319);
|
||||
|
||||
// 4. Hero Moment Return (12.239s - 13.739s)
|
||||
tl.to(frame, {
|
||||
scale: 1.0, /* Fixed return scale */
|
||||
opacity: 1,
|
||||
duration: 1.2,
|
||||
ease: 'back.out(1.2)'
|
||||
}, 12.239);
|
||||
|
||||
// 5. Final Exit (13.8s - 14s)
|
||||
tl.to(frame, {
|
||||
opacity: 0,
|
||||
scale: 0.6,
|
||||
duration: 0.5,
|
||||
ease: 'power2.in'
|
||||
}, 13.5);
|
||||
|
||||
window.__timelines["aroll"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,144 @@
|
||||
<template id="background-template">
|
||||
<div data-composition-id="background" data-width="1080" data-height="1920" data-duration="14">
|
||||
<div class="paper-texture"></div>
|
||||
|
||||
<!-- Grid Lines Container -->
|
||||
<div class="grid-container">
|
||||
<!-- Horizontal Lines -->
|
||||
<div class="line horizontal h1" style="top: 15%; height: 2px;"></div>
|
||||
<div class="line horizontal h2" style="top: 45%; height: 6px;"></div>
|
||||
<div class="line horizontal h3" style="top: 85%; height: 1px;"></div>
|
||||
|
||||
<!-- Vertical Lines -->
|
||||
<div class="line vertical v1" style="left: 20%; width: 4px;"></div>
|
||||
<div class="line vertical v2" style="left: 65%; width: 2px;"></div>
|
||||
<div class="line vertical v3" style="left: 80%; width: 8px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Bauhaus Red Circle -->
|
||||
<div class="circle-element"></div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="background"] {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-family: 'Helvetica', 'Arial', sans-serif;
|
||||
}
|
||||
|
||||
/* Subtle Paper Texture */
|
||||
[data-composition-id="background"] .paper-texture {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.1; /* Increased for tactile feel */
|
||||
pointer-events: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
[data-composition-id="background"] .grid-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
[data-composition-id="background"] .line {
|
||||
position: absolute;
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
[data-composition-id="background"] .horizontal {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
[data-composition-id="background"] .vertical {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
transform: scaleY(0);
|
||||
transform-origin: center top;
|
||||
}
|
||||
|
||||
[data-composition-id="background"] .circle-element {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
background-color: #BE1E2D;
|
||||
border-radius: 50%;
|
||||
top: 25%;
|
||||
left: 60%;
|
||||
transform: scale(0);
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// 1. Grid Lines Animation (0s - 1s)
|
||||
// Staggered entrance for horizontal lines
|
||||
tl.to('[data-composition-id="background"] .horizontal', {
|
||||
scaleX: 1,
|
||||
duration: 0.8,
|
||||
stagger: 0.15,
|
||||
ease: "power4.inOut"
|
||||
}, 0);
|
||||
|
||||
// Staggered entrance for vertical lines
|
||||
tl.to('[data-composition-id="background"] .vertical', {
|
||||
scaleY: 1,
|
||||
duration: 0.8,
|
||||
stagger: 0.1,
|
||||
ease: "power4.inOut"
|
||||
}, 0.2);
|
||||
|
||||
// 2. Circular Element Animation (0.5s - 1.2s)
|
||||
tl.to('[data-composition-id="background"] .circle-element', {
|
||||
scale: 1,
|
||||
duration: 0.7,
|
||||
ease: "circ.out"
|
||||
}, 0.5);
|
||||
|
||||
// 3. Ambient Motion (1s - 14s)
|
||||
// Subtle drift for the circle
|
||||
tl.to('[data-composition-id="background"] .circle-element', {
|
||||
x: -30,
|
||||
y: 20,
|
||||
duration: 6.5,
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
ease: "sine.inOut"
|
||||
}, 1);
|
||||
|
||||
// Subtle weight shift for lines (simulating paper expansion/contraction or focus shift)
|
||||
tl.to('[data-composition-id="background"] .h2', {
|
||||
height: "8px",
|
||||
duration: 6,
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
ease: "sine.inOut"
|
||||
}, 1);
|
||||
|
||||
tl.to('[data-composition-id="background"] .v3', {
|
||||
width: "12px",
|
||||
duration: 5,
|
||||
yoyo: true,
|
||||
repeat: 1,
|
||||
ease: "sine.inOut"
|
||||
}, 3);
|
||||
|
||||
// Register the timeline
|
||||
window.__timelines["background"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,187 @@
|
||||
<template id="captions-template">
|
||||
<div data-composition-id="captions" data-width="1080" data-height="1920" data-duration="14">
|
||||
<div class="captions-container">
|
||||
<!-- Groups will be injected here -->
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="captions"] {
|
||||
background: transparent;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; /* Akzidenz-Grotesk/Futura vibe */
|
||||
color: #000000;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .captions-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end; /* Align to bottom */
|
||||
padding-bottom: 30%; /* Position 30% from bottom of screen */
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .caption-group {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 85%; /* Slightly narrower to force wrapping */
|
||||
height: 250px; /* Reduced height to strictly enforce 2 lines */
|
||||
overflow: hidden; /* Enforce at most 2 lines */
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
text-transform: uppercase; /* Tschichold often used uppercase for impact */
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.1; /* Increased line height for breathing room */
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .word {
|
||||
display: inline-block;
|
||||
margin: 5px 15px; /* Added vertical margin */
|
||||
font-size: 80px; /* Supporting words standardized */
|
||||
font-weight: 300; /* Lighter weight for supporting words */
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
/* Extreme scale and weight for keywords */
|
||||
[data-composition-id="captions"] .word.bold {
|
||||
font-size: 120px; /* Reduced scale variance for better fit */
|
||||
font-weight: 900;
|
||||
color: #BE1E2D; /* Bauhaus Red for emphasis */
|
||||
}
|
||||
|
||||
/* Extreme weight for emphasis on keywords */
|
||||
[data-composition-id="captions"] .word.bold {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
/* Grid rail lines (decorative Tschichold elements) */
|
||||
[data-composition-id="captions"] .rail {
|
||||
position: absolute;
|
||||
background: #000000;
|
||||
opacity: 0.05;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .rail-h {
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .rail-v {
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const TRANSCRIPT = [{'text': 'We', 'start': 0.14, 'end': 0.239}, {'text': 'asked', 'start': 0.28, 'end': 0.459}, {'text': 'what', 'start': 0.5, 'end': 0.619}, {'text': 'you', 'start': 0.659, 'end': 0.779}, {'text': 'needed.', 'start': 0.8, 'end': 1.179}, {'text': 'Forty-seven', 'start': 1.199, 'end': 1.619}, {'text': 'percent', 'start': 1.699, 'end': 1.96}, {'text': 'of', 'start': 1.979, 'end': 2.059}, {'text': 'you', 'start': 2.099, 'end': 2.299}, {'text': 'said', 'start': 2.319, 'end': 2.499}, {'text': 'motion', 'start': 2.559, 'end': 2.839}, {'text': 'graphics,', 'start': 2.899, 'end': 3.759}, {'text': 'sixty-two', 'start': 3.799, 'end': 4.159}, {'text': 'percent', 'start': 4.239, 'end': 4.559}, {'text': 'said', 'start': 4.639, 'end': 4.799}, {'text': 'static', 'start': 4.859, 'end': 5.199}, {'text': 'content', 'start': 5.239, 'end': 5.639}, {'text': 'was', 'start': 5.679, 'end': 5.779}, {'text': 'costing', 'start': 5.859, 'end': 6.219}, {'text': 'you', 'start': 6.259, 'end': 6.339}, {'text': 'attention,', 'start': 6.379, 'end': 7.299}, {'text': 'and', 'start': 7.319, 'end': 7.439}, {'text': 'three', 'start': 7.48, 'end': 7.599}, {'text': 'out', 'start': 7.679, 'end': 7.779}, {'text': 'of', 'start': 7.799, 'end': 7.879}, {'text': 'four', 'start': 7.94, 'end': 8.139}, {'text': 'said', 'start': 8.279, 'end': 8.46}, {'text': 'you', 'start': 8.519, 'end': 8.6}, {'text': 'know', 'start': 8.619, 'end': 8.739}, {'text': 'the', 'start': 8.8, 'end': 8.88}, {'text': 'look', 'start': 8.92, 'end': 9.079}, {'text': 'you', 'start': 9.119, 'end': 9.259}, {'text': 'want', 'start': 9.279, 'end': 9.619}, {'text': 'but', 'start': 9.779, 'end': 9.88}, {'text': "don't", 'start': 9.92, 'end': 10.1}, {'text': 'have', 'start': 10.159, 'end': 10.3}, {'text': 'the', 'start': 10.3, 'end': 10.42}, {'text': 'editing', 'start': 10.5, 'end': 10.779}, {'text': 'skills', 'start': 10.86, 'end': 11.139}, {'text': 'to', 'start': 11.159, 'end': 11.239}, {'text': 'get', 'start': 11.279, 'end': 11.439}, {'text': 'there.', 'start': 11.46, 'end': 12.239}, {'text': 'So', 'start': 12.3, 'end': 12.38}, {'text': 'we', 'start': 12.399, 'end': 12.519}, {'text': 'built', 'start': 12.539, 'end': 12.759}, {'text': 'Editor', 'start': 12.84, 'end': 13.119}, {'text': 'Agent.', 'start': 13.239, 'end': 13.84}];
|
||||
const groups = [
|
||||
{ text: "We asked what you needed.", start: 0.14, end: 1.179 },
|
||||
{ text: "Forty-seven percent of you said motion graphics,", start: 1.199, end: 3.759 },
|
||||
{ text: "sixty-two percent said static content was costing you attention,", start: 3.799, end: 7.299 },
|
||||
{ text: "and three out of four said you know the look you want", start: 7.319, end: 9.619 },
|
||||
{ text: "but don't have the editing skills to get there.", start: 9.779, end: 12.239 },
|
||||
{ text: "So we built Editor Agent.", start: 12.3, end: 13.84 }
|
||||
];
|
||||
|
||||
const container = document.querySelector('[data-composition-id="captions"] .captions-container');
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Add background "rails" for Tschichold aesthetic - reduced to 2-3 strategic lines
|
||||
const railPositions = [
|
||||
{ type: 'h', pos: '30%' },
|
||||
{ type: 'h', pos: '70%' },
|
||||
{ type: 'v', pos: '25%' }
|
||||
];
|
||||
|
||||
railPositions.forEach(rail => {
|
||||
const el = document.createElement('div');
|
||||
el.className = `rail rail-${rail.type}`;
|
||||
if (rail.type === 'h') {
|
||||
el.style.top = rail.pos;
|
||||
} else {
|
||||
el.style.left = rail.pos;
|
||||
}
|
||||
container.appendChild(el);
|
||||
});
|
||||
|
||||
const keywords = ["asked", "needed", "forty-seven", "percent", "motion", "graphics", "sixty-two", "static", "content", "attention", "three", "four", "editing", "skills", "editor", "agent"];
|
||||
|
||||
groups.forEach((group, index) => {
|
||||
const groupEl = document.createElement('div');
|
||||
groupEl.className = 'caption-group';
|
||||
groupEl.id = 'group-' + index;
|
||||
|
||||
const words = group.text.split(' ');
|
||||
words.forEach(wordText => {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'word';
|
||||
const cleanWord = wordText.toLowerCase().replace(/[^\w]/g, '');
|
||||
if (keywords.includes(cleanWord)) {
|
||||
span.classList.add('bold');
|
||||
}
|
||||
span.textContent = wordText;
|
||||
groupEl.appendChild(span);
|
||||
});
|
||||
|
||||
container.appendChild(groupEl);
|
||||
|
||||
const groupWords = groupEl.querySelectorAll('.word');
|
||||
|
||||
// Entrance
|
||||
tl.set(groupEl, { visibility: 'visible' }, group.start);
|
||||
tl.to(groupEl, { opacity: 1, duration: 0.1 }, group.start);
|
||||
|
||||
// Words slide into alignment along "rails"
|
||||
tl.from(groupWords, {
|
||||
x: (i) => (i % 2 === 0 ? -150 : 150),
|
||||
opacity: 0,
|
||||
duration: 0.8,
|
||||
stagger: 0.04,
|
||||
ease: "power4.out"
|
||||
}, group.start);
|
||||
|
||||
// Exit
|
||||
const exitTime = group.end;
|
||||
tl.to(groupEl, {
|
||||
opacity: 0,
|
||||
duration: 0.2,
|
||||
ease: "power2.in"
|
||||
}, exitTime);
|
||||
tl.set(groupEl, { visibility: 'hidden' }, exitTime + 0.2);
|
||||
});
|
||||
|
||||
// Ambient motion for the rails
|
||||
tl.to('.rail-h', {
|
||||
scaleX: 0.9,
|
||||
opacity: 0.03,
|
||||
duration: 14,
|
||||
ease: "none"
|
||||
}, 0);
|
||||
|
||||
tl.to('.rail-v', {
|
||||
scaleY: 0.9,
|
||||
opacity: 0.03,
|
||||
duration: 14,
|
||||
ease: "none"
|
||||
}, 0);
|
||||
|
||||
window.__timelines["captions"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,300 @@
|
||||
<template id="hero-moment-template">
|
||||
<div data-composition-id="hero-moment" data-width="1080" data-height="1920" data-duration="14">
|
||||
<div class="background">
|
||||
<div class="paper-texture"></div>
|
||||
<div class="grid-lines"></div>
|
||||
<div class="accent-circle"></div>
|
||||
</div>
|
||||
|
||||
<div class="content-layer">
|
||||
<div class="stat-container">
|
||||
<div class="stat-number">75%</div>
|
||||
<div class="stat-fraction">(3 out of 4)</div>
|
||||
</div>
|
||||
|
||||
<div class="text-container">
|
||||
<div class="line line-1">LACK</div>
|
||||
<div class="line line-2">THE ESSENTIAL</div>
|
||||
<div class="line line-3">SKILLS</div>
|
||||
</div>
|
||||
|
||||
<div class="geometric-elements">
|
||||
<div class="rule-line horizontal"></div>
|
||||
<div class="rule-line vertical"></div>
|
||||
<div class="square-accent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="hero-moment"] {
|
||||
background-color: transparent;
|
||||
color: #000000;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .paper-texture {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.05; /* Standardized to 0.05 */
|
||||
pointer-events: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .grid-lines {
|
||||
position: absolute;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px);
|
||||
background-size: 100px 100px;
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .accent-circle {
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
border: 2px solid #BE1E2D;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .content-layer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 900px;
|
||||
height: 1600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .stat-container {
|
||||
position: absolute;
|
||||
top: 10%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .stat-number {
|
||||
font-size: 280px; /* Reduced massiveness for better hierarchy */
|
||||
font-weight: 300; /* Slightly more weight for legibility */
|
||||
line-height: 0.8;
|
||||
color: #BE1E2D;
|
||||
letter-spacing: -10px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .stat-fraction {
|
||||
font-size: 48px; /* Slightly larger supporting text */
|
||||
font-weight: 300;
|
||||
margin-top: 10px;
|
||||
letter-spacing: 12px; /* More tracking */
|
||||
text-transform: uppercase;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .text-container {
|
||||
position: absolute;
|
||||
bottom: 15%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .line {
|
||||
font-size: 120px; /* Increased for impact */
|
||||
font-weight: 800;
|
||||
line-height: 0.8; /* Tighter line height */
|
||||
margin-bottom: 10px;
|
||||
letter-spacing: 4px; /* More tracking */
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .line-2 {
|
||||
font-size: 60px;
|
||||
font-weight: 400;
|
||||
color: #BE1E2D;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .geometric-elements {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .rule-line {
|
||||
position: absolute;
|
||||
background-color: #000000;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .rule-line.horizontal {
|
||||
width: 0%;
|
||||
height: 4px;
|
||||
top: 45%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .rule-line.vertical {
|
||||
width: 4px;
|
||||
height: 0%;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
[data-composition-id="hero-moment"] .square-accent {
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-color: #BE1E2D;
|
||||
top: 43.5%;
|
||||
left: 47.5%;
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const TRANSCRIPT = [{'text': 'We', 'start': 0.14, 'end': 0.239}, {'text': 'asked', 'start': 0.28, 'end': 0.459}, {'text': 'what', 'start': 0.5, 'end': 0.619}, {'text': 'you', 'start': 0.659, 'end': 0.779}, {'text': 'needed.', 'start': 0.8, 'end': 1.179}, {'text': 'Forty-seven', 'start': 1.199, 'end': 1.619}, {'text': 'percent', 'start': 1.699, 'end': 1.96}, {'text': 'of', 'start': 1.979, 'end': 2.059}, {'text': 'you', 'start': 2.099, 'end': 2.299}, {'text': 'said', 'start': 2.319, 'end': 2.499}, {'text': 'motion', 'start': 2.559, 'end': 2.839}, {'text': 'graphics,', 'start': 2.899, 'end': 3.759}, {'text': 'sixty-two', 'start': 3.799, 'end': 4.159}, {'text': 'percent', 'start': 4.239, 'end': 4.559}, {'text': 'said', 'start': 4.639, 'end': 4.799}, {'text': 'static', 'start': 4.859, 'end': 5.199}, {'text': 'content', 'start': 5.239, 'end': 5.639}, {'text': 'was', 'start': 5.679, 'end': 5.779}, {'text': 'costing', 'start': 5.859, 'end': 6.219}, {'text': 'you', 'start': 6.259, 'end': 6.339}, {'text': 'attention,', 'start': 6.379, 'end': 7.299}, {'text': 'and', 'start': 7.319, 'end': 7.439}, {'text': 'three', 'start': 7.48, 'end': 7.599}, {'text': 'out', 'start': 7.679, 'end': 7.779}, {'text': 'of', 'start': 7.799, 'end': 7.879}, {'text': 'four', 'start': 7.94, 'end': 8.139}, {'text': 'said', 'start': 8.279, 'end': 8.46}, {'text': 'you', 'start': 8.519, 'end': 8.6}, {'text': 'know', 'start': 8.619, 'end': 8.739}, {'text': 'the', 'start': 8.8, 'end': 8.88}, {'text': 'look', 'start': 8.92, 'end': 9.079}, {'text': 'you', 'start': 9.119, 'end': 9.259}, {'text': 'want', 'start': 9.279, 'end': 9.619}, {'text': 'but', 'start': 9.779, 'end': 9.88}, {'text': "don't", 'start': 9.92, 'end': 10.1}, {'text': 'have', 'start': 10.159, 'end': 10.3}, {'text': 'the', 'start': 10.3, 'end': 10.42}, {'text': 'editing', 'start': 10.5, 'end': 10.779}, {'text': 'skills', 'start': 10.86, 'end': 11.139}, {'text': 'to', 'start': 11.159, 'end': 11.239}, {'text': 'get', 'start': 11.279, 'end': 11.439}, {'text': 'there.', 'start': 11.46, 'end': 12.239}, {'text': 'So', 'start': 12.3, 'end': 12.38}, {'text': 'we', 'start': 12.399, 'end': 12.519}, {'text': 'built', 'start': 12.539, 'end': 12.759}, {'text': 'Editor', 'start': 12.84, 'end': 13.119}, {'text': 'Agent.', 'start': 13.239, 'end': 13.84}];
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Helper to find word timing
|
||||
function getWordTime(word) {
|
||||
const found = TRANSCRIPT.find(w => w.text.toLowerCase().replace(/[^\w]/g, '') === word.toLowerCase());
|
||||
return found ? found.start : 0;
|
||||
}
|
||||
|
||||
// 1. Entrance (Starts at 7.319s)
|
||||
tl.to('[data-composition-id="hero-moment"] .accent-circle', {
|
||||
opacity: 0.2,
|
||||
scale: 1.2,
|
||||
duration: 1,
|
||||
ease: 'power2.out'
|
||||
}, 7.319);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .rule-line.horizontal', {
|
||||
width: '100%',
|
||||
opacity: 0.3,
|
||||
duration: 0.8,
|
||||
ease: 'expo.out'
|
||||
}, 7.4);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .rule-line.vertical', {
|
||||
height: '100%',
|
||||
opacity: 0.3,
|
||||
duration: 0.8,
|
||||
ease: 'expo.out'
|
||||
}, 7.5);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .square-accent', {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
duration: 0.5,
|
||||
ease: 'back.out(1.7)'
|
||||
}, 7.8);
|
||||
|
||||
// 2. Kinetic Typography synced to transcript
|
||||
// "75%" (three out of four)
|
||||
const timeThree = getWordTime('three');
|
||||
tl.to('[data-composition-id="hero-moment"] .stat-number', {
|
||||
opacity: 1,
|
||||
duration: 0.8,
|
||||
ease: 'power4.out'
|
||||
}, timeThree);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .stat-fraction', {
|
||||
opacity: 0.8,
|
||||
duration: 0.8,
|
||||
ease: 'power2.out'
|
||||
}, timeThree + 0.2);
|
||||
|
||||
// "lack"
|
||||
const timeSkills = getWordTime('skills');
|
||||
tl.to('[data-composition-id="hero-moment"] .line-1', {
|
||||
opacity: 1,
|
||||
duration: 0.6,
|
||||
ease: 'back.out(1.2)'
|
||||
}, timeSkills - 0.4);
|
||||
|
||||
// "essential"
|
||||
tl.to('[data-composition-id="hero-moment"] .line-2', {
|
||||
opacity: 1,
|
||||
duration: 0.6,
|
||||
ease: 'back.out(1.2)'
|
||||
}, timeSkills - 0.2);
|
||||
|
||||
// "skills"
|
||||
tl.to('[data-composition-id="hero-moment"] .line-3', {
|
||||
opacity: 1,
|
||||
duration: 0.6,
|
||||
ease: 'back.out(1.2)'
|
||||
}, timeSkills);
|
||||
|
||||
// 3. Ambient Motion (Continuous)
|
||||
tl.to('[data-composition-id="hero-moment"] .grid-lines', {
|
||||
x: '+=50',
|
||||
y: '+=50',
|
||||
duration: 14,
|
||||
ease: 'none'
|
||||
}, 0);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .accent-circle', {
|
||||
scale: 1.3,
|
||||
duration: 14,
|
||||
ease: 'sine.inOut'
|
||||
}, 0);
|
||||
|
||||
// 4. Exit (Starts at 12.239s)
|
||||
tl.to('[data-composition-id="hero-moment"] .content-layer, [data-composition-id="hero-moment"] .geometric-elements', {
|
||||
opacity: 0,
|
||||
y: 50,
|
||||
duration: 0.6,
|
||||
ease: 'power3.in'
|
||||
}, 12.239);
|
||||
|
||||
tl.to('[data-composition-id="hero-moment"] .accent-circle', {
|
||||
scale: 2,
|
||||
opacity: 0,
|
||||
duration: 0.8,
|
||||
ease: 'power2.in'
|
||||
}, 12.239);
|
||||
|
||||
window.__timelines["hero-moment"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template id="mograph-overlays-template">
|
||||
<div data-composition-id="mograph-overlays" data-width="1080" data-height="1920" data-duration="14">
|
||||
<div id="mograph-container">
|
||||
<!-- Ambient Elements -->
|
||||
<div class="diagonal-line line-1"></div>
|
||||
<div class="diagonal-line line-2"></div>
|
||||
<div class="marker marker-tl"></div>
|
||||
<div class="marker marker-tr"></div>
|
||||
<div class="marker marker-bl"></div>
|
||||
<div class="marker marker-br"></div>
|
||||
|
||||
<!-- Alignment Indicators -->
|
||||
<div class="alignment-h"></div>
|
||||
<div class="alignment-v"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="mograph-overlays"] {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] #mograph-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Diagonal Lines - Modernist Red */
|
||||
[data-composition-id="mograph-overlays"] .diagonal-line {
|
||||
position: absolute;
|
||||
background: #BE1E2D;
|
||||
height: 1px;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .line-1 {
|
||||
width: 600px;
|
||||
top: 150px;
|
||||
left: -150px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .line-2 {
|
||||
width: 800px;
|
||||
bottom: 200px;
|
||||
right: -200px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
/* Geometric Markers - Clean Lines */
|
||||
[data-composition-id="mograph-overlays"] .marker {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 1px solid #000000;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .marker-tl { top: 60px; left: 60px; border-right: 0; border-bottom: 0; }
|
||||
[data-composition-id="mograph-overlays"] .marker-tr { top: 60px; right: 60px; border-left: 0; border-bottom: 0; }
|
||||
[data-composition-id="mograph-overlays"] .marker-bl { bottom: 60px; left: 60px; border-right: 0; border-top: 0; }
|
||||
[data-composition-id="mograph-overlays"] .marker-br { bottom: 60px; right: 60px; border-left: 0; border-top: 0; }
|
||||
|
||||
/* Alignment Indicators - Subtle Grid */
|
||||
[data-composition-id="mograph-overlays"] .alignment-h {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 80px;
|
||||
right: 80px;
|
||||
height: 1px;
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .alignment-v {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 80px;
|
||||
bottom: 80px;
|
||||
width: 1px;
|
||||
background: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Dynamic Graphic - Modernist Museum Style */
|
||||
[data-composition-id="mograph-overlays"] .dynamic-graphic {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .graphic-value {
|
||||
font-size: 80px;
|
||||
font-weight: 300; /* Lighter weight for museum feel */
|
||||
line-height: 0.9;
|
||||
color: #000000;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .graphic-label {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 6px;
|
||||
margin-top: 15px;
|
||||
color: #BE1E2D; /* Accent color for label */
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-composition-id="mograph-overlays"] .graphic-line {
|
||||
width: 0;
|
||||
height: 1px;
|
||||
background: #000000;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const TRANSCRIPT = [{'text': 'We', 'start': 0.14, 'end': 0.239}, {'text': 'asked', 'start': 0.28, 'end': 0.459}, {'text': 'what', 'start': 0.5, 'end': 0.619}, {'text': 'you', 'start': 0.659, 'end': 0.779}, {'text': 'needed.', 'start': 0.8, 'end': 1.179}, {'text': 'Forty-seven', 'start': 1.199, 'end': 1.619}, {'text': 'percent', 'start': 1.699, 'end': 1.96}, {'text': 'of', 'start': 1.979, 'end': 2.059}, {'text': 'you', 'start': 2.099, 'end': 2.299}, {'text': 'said', 'start': 2.319, 'end': 2.499}, {'text': 'motion', 'start': 2.559, 'end': 2.839}, {'text': 'graphics,', 'start': 2.899, 'end': 3.759}, {'text': 'sixty-two', 'start': 3.799, 'end': 4.159}, {'text': 'percent', 'start': 4.239, 'end': 4.559}, {'text': 'said', 'start': 4.639, 'end': 4.799}, {'text': 'static', 'start': 4.859, 'end': 5.199}, {'text': 'content', 'start': 5.239, 'end': 5.639}, {'text': 'was', 'start': 5.679, 'end': 5.779}, {'text': 'costing', 'start': 5.859, 'end': 6.219}, {'text': 'you', 'start': 6.259, 'end': 6.339}, {'text': 'attention,', 'start': 6.379, 'end': 7.299}, {'text': 'and', 'start': 7.319, 'end': 7.439}, {'text': 'three', 'start': 7.48, 'end': 7.599}, {'text': 'out', 'start': 7.679, 'end': 7.779}, {'text': 'of', 'start': 7.799, 'end': 7.879}, {'text': 'four', 'start': 7.94, 'end': 8.139}, {'text': 'said', 'start': 8.279, 'end': 8.46}, {'text': 'you', 'start': 8.519, 'end': 8.6}, {'text': 'know', 'start': 8.619, 'end': 8.739}, {'text': 'the', 'start': 8.8, 'end': 8.88}, {'text': 'look', 'start': 8.92, 'end': 9.079}, {'text': 'you', 'start': 9.119, 'end': 9.259}, {'text': 'want', 'start': 9.279, 'end': 9.619}, {'text': 'but', 'start': 9.779, 'end': 9.88}, {'text': "don't", 'start': 9.92, 'end': 10.1}, {'text': 'have', 'start': 10.159, 'end': 10.3}, {'text': 'the', 'start': 10.3, 'end': 10.42}, {'text': 'editing', 'start': 10.5, 'end': 10.779}, {'text': 'skills', 'start': 10.86, 'end': 11.139}, {'text': 'to', 'start': 11.159, 'end': 11.239}, {'text': 'get', 'start': 11.279, 'end': 11.439}, {'text': 'there.', 'start': 11.46, 'end': 12.239}, {'text': 'So', 'start': 12.3, 'end': 12.38}, {'text': 'we', 'start': 12.399, 'end': 12.519}, {'text': 'built', 'start': 12.539, 'end': 12.759}, {'text': 'Editor', 'start': 12.84, 'end': 13.119}, {'text': 'Agent.', 'start': 13.239, 'end': 13.84}];
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Face Avoidance Area: x:300-776, y:320-796
|
||||
// Safe zones: Right side (x > 800), Top (y < 300), Bottom (y > 800)
|
||||
|
||||
// 1. Ambient Motion (Continuous & Subtle)
|
||||
tl.to('.line-1', { x: 30, y: 10, duration: 14, ease: 'none' }, 0);
|
||||
tl.to('.line-2', { x: -30, y: -10, duration: 14, ease: 'none' }, 0);
|
||||
|
||||
tl.from('.marker', {
|
||||
scale: 0.9,
|
||||
opacity: 0,
|
||||
duration: 1.5,
|
||||
stagger: 0.3,
|
||||
ease: 'power2.out'
|
||||
}, 0);
|
||||
|
||||
// 2. Transcript-Synced Graphics
|
||||
// Updated content based on design review
|
||||
const triggerWords = [
|
||||
{ word: "47%", label: "USER SURVEY" },
|
||||
{ word: "static", label: "CONTENT ENGAGEMENT" },
|
||||
{ word: "editing", label: "TECHNICAL SKILLS" },
|
||||
{ word: "Editor", label: "SYSTEM ARCHITECTURE" }
|
||||
];
|
||||
|
||||
triggerWords.forEach((trigger, index) => {
|
||||
const wordData = TRANSCRIPT.find(w => w.text.toLowerCase().includes(trigger.word.toLowerCase()));
|
||||
if (wordData) {
|
||||
const startTime = wordData.start;
|
||||
const duration = 3.0;
|
||||
|
||||
// Deterministic positioning outside face area (Vertical Safe Zones)
|
||||
// Face: x:300-776, y:320-796 (approx in vertical)
|
||||
const positions = [
|
||||
{ x: 50, y: 1500 }, // Bottom Left
|
||||
{ x: 600, y: 1600 }, // Bottom Right
|
||||
{ x: 100, y: 100 }, // Top Left
|
||||
{ x: 500, y: 50 } // Top Right
|
||||
];
|
||||
const pos = positions[index % positions.length];
|
||||
|
||||
// Create a unique graphic for each trigger
|
||||
const graphicId = `graphic-${index}`;
|
||||
const graphicHtml = `
|
||||
<div id="${graphicId}" class="dynamic-graphic" style="left: ${pos.x}px; top: ${pos.y}px;">
|
||||
<div class="graphic-value">${trigger.word.toUpperCase()}</div>
|
||||
<div class="graphic-label">${trigger.label}</div>
|
||||
<div class="graphic-line"></div>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('mograph-container').insertAdjacentHTML('beforeend', graphicHtml);
|
||||
|
||||
const graphicSelector = `#${graphicId}`;
|
||||
|
||||
// Entrance - Clean and Sophisticated
|
||||
tl.to(graphicSelector, {
|
||||
opacity: 1,
|
||||
duration: 1.0,
|
||||
ease: 'power3.out'
|
||||
}, startTime);
|
||||
|
||||
tl.from(`${graphicSelector} .graphic-value`, {
|
||||
x: -20,
|
||||
opacity: 0,
|
||||
duration: 1.2,
|
||||
ease: 'power4.out'
|
||||
}, startTime + 0.1);
|
||||
|
||||
tl.to(`${graphicSelector} .graphic-line`, {
|
||||
width: 250,
|
||||
duration: 1.5,
|
||||
ease: 'expo.out'
|
||||
}, startTime + 0.4);
|
||||
|
||||
// Exit - Smooth Fade
|
||||
tl.to(graphicSelector, {
|
||||
opacity: 0,
|
||||
x: 20,
|
||||
duration: 0.8,
|
||||
ease: 'power2.in'
|
||||
}, startTime + duration);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Subtle Grid/Alignment pulses
|
||||
tl.to('.alignment-h, .alignment-v', {
|
||||
opacity: 0.2,
|
||||
duration: 0.5,
|
||||
repeat: 3,
|
||||
yoyo: true,
|
||||
ease: 'power1.inOut'
|
||||
}, 1);
|
||||
|
||||
window.__timelines["mograph-overlays"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
# HyperFrames Design Review
|
||||
|
||||
## First Impression
|
||||
This looks like a Bauhaus-inspired museum exhibit had a head-on collision with a TikTok ad. It’s trying to be Jan Tschichold, but it’s currently closer to "I just discovered the Helvetica font and the color red."
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL Design Failures
|
||||
|
||||
Issues that make this look unprofessional or straight-up ugly. These MUST be fixed.
|
||||
|
||||
### The "Floating Head" Problem
|
||||
**Where:** `compositions/aroll.html`
|
||||
**What's wrong:** The A-Roll video is set to `object-fit: cover` and fills the entire 1080x1920 frame. While bold, it completely ignores the "safe zones" for the face. The `mograph_overlays.html` then proceeds to slap text directly over where the speaker's face likely is.
|
||||
**Why it matters:** You're literally covering the speaker's mouth or eyes with "USER SURVEY" and "47%". It looks like a mistake, not a design choice.
|
||||
**Fix it:** Scale the A-Roll video down or use a mask that respects the rule of thirds. If you're going full-screen, your overlays need to be pushed to the extreme margins.
|
||||
|
||||
### Caption Chaos
|
||||
**Where:** `compositions/captions.html`
|
||||
**What's wrong:** You have words jumping from `font-size: 50px` to `140px` with a `line-height: 0.9`. In a 9:16 portrait format, this is going to cause massive, ugly overlaps and erratic wrapping.
|
||||
**Why it matters:** Readability is the first casualty of "cool" design. If the viewer has to squint or decode which word comes next because they're stacked like a game of Tetris, you've failed.
|
||||
**Fix it:** Standardize the scale variance. 50px to 140px is too wide a gap. Try 80px to 120px. Increase the `line-height` to at least `1.1` to give the descenders some breathing room.
|
||||
|
||||
### The "Dead" Background
|
||||
**Where:** `compositions/background.html`
|
||||
**What's wrong:** A `#FAF5E8` (off-white) background with a `0.05` opacity noise filter is basically just a flat, boring beige on most mobile screens. The grid lines are too thin (`1px`, `2px`) to feel intentional; they just look like screen artifacts.
|
||||
**Why it matters:** It lacks depth. It feels like a default template rather than a premium "New Typography" aesthetic.
|
||||
**Fix it:** Increase the noise opacity to `0.1` or `0.15`. Make the grid lines bolder—use `4px` and `8px` consistently to create a real sense of structure.
|
||||
|
||||
---
|
||||
|
||||
## Design Improvements
|
||||
|
||||
Things that aren't broken but are boring, lazy, or could be significantly better.
|
||||
|
||||
### Robotic Motion
|
||||
**Where:** `compositions/mograph_overlays.html`
|
||||
**The problem:** The diagonal lines and markers just "drift" linearly. It’s the most basic GSAP animation possible.
|
||||
**Make it better:** Add some "snap" to the entrance. Use `expo.out` or `back.out` for the markers. Give the lines a slight pulse in opacity or thickness to make them feel like they're reacting to the audio, not just floating in space.
|
||||
|
||||
### Hero Moment Hierarchy
|
||||
**Where:** `compositions/hero_moment.html`
|
||||
**The problem:** The "75%" is `280px` while the supporting text is `120px`. It’s a bit of a "shouting match" between elements.
|
||||
**Make it better:** Lean into the Tschichold asymmetry. Move the "75%" off-center. Use a much heavier weight for the number and a much lighter, tracked-out weight for the text. Contrast is your friend; stop making everything "big."
|
||||
|
||||
---
|
||||
|
||||
## What Actually Works
|
||||
|
||||
The use of **Bauhaus Red (#BE1E2D)** against the off-white is a classic for a reason. When it hits, it hits hard. The "rail" concept in the captions is a genuine nod to modernist layout principles—it just needs better execution. The "wipe-line" entrance in the A-Roll is actually quite sophisticated and provides a nice "editorial" feel.
|
||||
|
||||
---
|
||||
|
||||
## Design Verdict
|
||||
|
||||
**Visual Impact:** 6/10 - It has a clear direction, but it's playing it too safe.
|
||||
**Color & Typography:** 5/10 - Good palette, but the typographic hierarchy is a mess of competing scales.
|
||||
**Motion & Animation Feel:** 4/10 - Too much linear drifting; needs more rhythmic "pop."
|
||||
**Overall Aesthetic:** 5/10 - It looks like a student project trying to mimic a master.
|
||||
|
||||
**Bottom Line:** It’s not a disaster, but it’s forgettable. You’ve got the ingredients for a high-end editorial look, but you’re currently serving it on a paper plate. Fix the face-blocking overlays and the caption scaling before you show this to anyone with a pulse.
|
||||
@@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jan Tschichold Typography Video</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: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
background-color: #FAF5E8;
|
||||
}
|
||||
#master-composition {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
#bg-comp { z-index: 1; }
|
||||
#aroll-comp { z-index: 2; }
|
||||
#hero-comp { z-index: 3; }
|
||||
#mograph-comp { z-index: 4; }
|
||||
#captions-comp { z-index: 5; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="master-composition" data-composition-id="master" data-width="1080" data-height="1920" data-duration="14">
|
||||
|
||||
<!-- Background Layer -->
|
||||
<div id="bg-comp"
|
||||
data-composition-id="background"
|
||||
data-composition-src="compositions/background.html"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="0">
|
||||
</div>
|
||||
|
||||
<!-- A-Roll Video Layer -->
|
||||
<div id="aroll-comp"
|
||||
data-composition-id="aroll"
|
||||
data-composition-src="compositions/aroll.html"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="1">
|
||||
</div>
|
||||
|
||||
<!-- Hero Moment (Full Canvas) -->
|
||||
<div id="hero-comp"
|
||||
data-composition-id="hero-moment"
|
||||
data-composition-src="compositions/hero_moment.html"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="2">
|
||||
</div>
|
||||
|
||||
<!-- Motion Graphic Overlays -->
|
||||
<div id="mograph-comp"
|
||||
data-composition-id="mograph-overlays"
|
||||
data-composition-src="compositions/mograph_overlays.html"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="3">
|
||||
</div>
|
||||
|
||||
<!-- Captions Layer -->
|
||||
<div id="captions-comp"
|
||||
data-composition-id="captions"
|
||||
data-composition-src="compositions/captions.html"
|
||||
data-start="0"
|
||||
data-duration="14"
|
||||
data-track-index="4">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.__timelines = {};
|
||||
const masterTL = gsap.timeline({ paused: true });
|
||||
window.__timelines["master"] = masterTL;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user