mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-14 00:00:17 +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,17 @@
|
||||
{
|
||||
"name": "style-4-prod",
|
||||
"description": "Regression fixture imported from normalized style pack style-4-prod",
|
||||
"tags": [
|
||||
"style-regression",
|
||||
"prod-style",
|
||||
"slow",
|
||||
"landscape"
|
||||
],
|
||||
"minPsnr": 30,
|
||||
"maxFrameFailures": 0,
|
||||
"minAudioCorrelation": 0.9,
|
||||
"maxAudioLagWindows": 120,
|
||||
"renderConfig": {
|
||||
"fps": 30
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
# HyperFrame Schema Compliance Review
|
||||
|
||||
## Executive Summary
|
||||
- Total files reviewed: 4
|
||||
- Critical issues: 0
|
||||
- Overall compliance status: PASS
|
||||
|
||||
## Critical Issues
|
||||
None found. The compositions follow the deterministic requirements and structural rules of the HyperFrame schema.
|
||||
|
||||
## 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 (N/A - no images used)
|
||||
- [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 (N/A - absolute timing used)
|
||||
- [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
|
||||
|
||||
## File Analysis
|
||||
|
||||
### index.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Observations**:
|
||||
- Correctly defines the root composition `main-video`.
|
||||
- Uses `data-composition-src` to load sub-compositions.
|
||||
- Properly registers the master timeline in `window.__timelines`.
|
||||
- Uses deterministic GSAP animations for the A-roll video.
|
||||
- Note: The `grain-overlay` is defined as an inline composition, which is acceptable for simple effects.
|
||||
|
||||
### compositions/intro.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Observations**:
|
||||
- Uses `<template>` tag as required.
|
||||
- Root element has `data-composition-id`, `data-width`, `data-height`, and `data-duration`.
|
||||
- Timeline is correctly registered and scoped within an IIFE.
|
||||
- Animation is deterministic.
|
||||
|
||||
### compositions/captions.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Observations**:
|
||||
- Uses `<template>` tag.
|
||||
- Correctly handles dynamic caption generation within a composition.
|
||||
- Timeline registration is correct.
|
||||
- Deterministic timing based on a static script array.
|
||||
|
||||
### compositions/graphics.html
|
||||
**Status**: COMPLIANT
|
||||
|
||||
**Observations**:
|
||||
- Uses `<template>` tag.
|
||||
- Root element has all required data attributes.
|
||||
- GSAP animations are deterministic and use explicit durations.
|
||||
- Timeline registration is correct.
|
||||
@@ -0,0 +1,134 @@
|
||||
<template id="captions-template">
|
||||
<div data-composition-id="captions" data-width="1920" data-height="1080" data-duration="18">
|
||||
<div class="captions-container">
|
||||
<div id="caption-box" class="caption-box">
|
||||
<span id="caption-text" class="caption-text"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="captions"] .captions-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 230px; /* Position at y: 850 (1080 - 230 = 850) */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .caption-box {
|
||||
background-color: #7A6248;
|
||||
padding: 12px 32px;
|
||||
border-radius: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-width: 100px;
|
||||
max-width: 80%;
|
||||
opacity: 0;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
[data-composition-id="captions"] .caption-text {
|
||||
color: #F5F0E0;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const script = [
|
||||
{"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.86, "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.599},
|
||||
{"text": "sixty-two", "start": 4.679, "end": 5.179},
|
||||
{"text": "percent", "start": 5.299, "end": 5.759},
|
||||
{"text": "said", "start": 5.859, "end": 5.98},
|
||||
{"text": "static", "start": 6.079, "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.42},
|
||||
{"text": "look", "start": 10.519, "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.02}
|
||||
];
|
||||
|
||||
// Group words into lines (max 5 words per line)
|
||||
const lines = [];
|
||||
for (let i = 0; i < script.length; i += 5) {
|
||||
const lineWords = script.slice(i, i + 5);
|
||||
lines.push({
|
||||
text: lineWords.map(w => w.text).join(' '),
|
||||
start: lineWords[0].start,
|
||||
end: lineWords[lineWords.length - 1].end
|
||||
});
|
||||
}
|
||||
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
const box = document.querySelector('[data-composition-id="captions"] #caption-box');
|
||||
const textEl = document.querySelector('[data-composition-id="captions"] #caption-text');
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
// Fade in and set text
|
||||
tl.to(box, {
|
||||
opacity: 1,
|
||||
duration: 0.1,
|
||||
ease: "power2.out",
|
||||
onStart: () => {
|
||||
textEl.textContent = line.text;
|
||||
}
|
||||
}, line.start);
|
||||
|
||||
// Fade out at the end of the line
|
||||
tl.to(box, {
|
||||
opacity: 0,
|
||||
duration: 0.1,
|
||||
ease: "power2.in"
|
||||
}, line.end);
|
||||
});
|
||||
|
||||
window.__timelines["captions"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template id="graphics-template">
|
||||
<div data-composition-id="graphics" data-width="1920" data-height="1080" data-duration="14">
|
||||
<!-- Moment 1: 47% Stat (1.8s - 4.5s) -->
|
||||
<div id="moment-1" class="moment circle-stat">
|
||||
<div class="stat-content">
|
||||
<div class="stat-text">47%</div>
|
||||
<div class="stat-desc">Need Motion Graphics</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Moment 2: 62% Stat (4.6s - 8.6s) -->
|
||||
<div id="moment-2" class="moment pill-stat">
|
||||
<div class="stat-text">62%</div>
|
||||
</div>
|
||||
|
||||
<!-- Moment 3: Editing Skills (8.8s - 14s) -->
|
||||
<div id="moment-3" class="moment rect-stat">
|
||||
<div class="icon-shape">
|
||||
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20,20 Q50,0 80,20 Q100,50 80,80 Q50,100 20,80 Q0,50 20,20" fill="currentColor" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-text">Editing Skills</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
[data-composition-id="graphics"] {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .moment {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
/* We'll use GSAP to handle the centering and drift */
|
||||
}
|
||||
|
||||
/* Moment 1: Forest Green Circle */
|
||||
[data-composition-id="graphics"] #moment-1 {
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
background-color: #3B5E3A;
|
||||
border-radius: 50%;
|
||||
left: 1400px;
|
||||
top: 540px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .stat-desc {
|
||||
font-size: 36px;
|
||||
font-weight: 400;
|
||||
margin-top: 10px;
|
||||
max-width: 300px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Moment 2: Ochre Pill */
|
||||
[data-composition-id="graphics"] #moment-2 {
|
||||
width: 500px;
|
||||
height: 220px;
|
||||
background-color: #CC8832;
|
||||
border-radius: 110px;
|
||||
left: 960px;
|
||||
top: 200px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* Moment 3: Terracotta Rounded Rect */
|
||||
[data-composition-id="graphics"] #moment-3 {
|
||||
width: 550px;
|
||||
height: 180px;
|
||||
background-color: #C45D3E;
|
||||
border-radius: 40px;
|
||||
left: 400px;
|
||||
top: 540px;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 0 40px;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .stat-text {
|
||||
font-size: 80px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] #moment-3 .stat-text {
|
||||
font-size: 54px;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .icon-shape {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
[data-composition-id="graphics"] .icon-shape svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Moment 1: 1.8s - 4.5s
|
||||
// Gentle drift into place and settle
|
||||
tl.fromTo('#moment-1',
|
||||
{ opacity: 0, x: '+=40', y: '+=15', scale: 0.8 },
|
||||
{ opacity: 1, x: 0, y: 0, scale: 1, duration: 1.2, ease: 'back.out(1.7)' },
|
||||
1.8
|
||||
);
|
||||
// Hold until 4.5s, then fade out
|
||||
tl.to('#moment-1', { opacity: 0, scale: 0.8, duration: 0.3, ease: 'power2.in' }, 4.2);
|
||||
|
||||
// Moment 2: 4.6s - 8.6s
|
||||
// Gentle drift into place and settle
|
||||
tl.fromTo('#moment-2',
|
||||
{ opacity: 0, y: '-=40', x: '+=10', scale: 0.8 },
|
||||
{ opacity: 1, y: 0, x: 0, scale: 1, duration: 1.2, ease: 'back.out(1.7)' },
|
||||
4.6
|
||||
);
|
||||
// Hold until 8.6s, then fade out
|
||||
tl.to('#moment-2', { opacity: 0, scale: 0.8, duration: 0.3, ease: 'power2.in' }, 8.3);
|
||||
|
||||
// Moment 3: 8.8s - 14s
|
||||
// Gentle drift into place and settle
|
||||
tl.fromTo('#moment-3',
|
||||
{ opacity: 0, x: '-=40', y: '-=10', scale: 0.8 },
|
||||
{ opacity: 1, x: 0, y: 0, scale: 1, duration: 1.2, ease: 'back.out(1.7)' },
|
||||
8.8
|
||||
);
|
||||
// Hold until end (14s)
|
||||
tl.to('#moment-3', { opacity: 0, duration: 0.5, ease: 'power2.in' }, 13.5);
|
||||
|
||||
window.__timelines["graphics"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,77 @@
|
||||
<template id="intro-template">
|
||||
<div data-composition-id="intro" data-width="1920" data-height="1080" data-duration="3">
|
||||
<div class="container">
|
||||
<div class="title-card">
|
||||
<h1 class="title">Editor Agent</h1>
|
||||
<p class="subtitle">Design simplified.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Import a rounded humanist sans-serif font */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;600&display=swap');
|
||||
|
||||
[data-composition-id="intro"] .container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-start; /* Align to left for speaker card */
|
||||
align-items: center;
|
||||
padding-left: 5%;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
[data-composition-id="intro"] .title-card {
|
||||
background-color: #3B5E3A; /* Forest Green for contrast */
|
||||
padding: 40px 60px;
|
||||
border-radius: 30px;
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.2);
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
transform: translateX(-100%); /* Start off-screen left */
|
||||
}
|
||||
|
||||
[data-composition-id="intro"] .title {
|
||||
font-size: 100px;
|
||||
font-weight: 600;
|
||||
color: #F5F0E0; /* Cream text on green card */
|
||||
margin: 0;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -2px;
|
||||
}
|
||||
|
||||
[data-composition-id="intro"] .subtitle {
|
||||
font-size: 50px;
|
||||
font-weight: 400;
|
||||
color: #CC8832; /* Ochre accent */
|
||||
margin: 10px 0 0 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// Animation: Speaker card slides in from the left
|
||||
tl.to('[data-composition-id="intro"] .title-card', {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
duration: 0.8,
|
||||
ease: 'power2.out'
|
||||
}, 0.2);
|
||||
|
||||
// Exit: Slide off left at 1.8s (synced with 47% graphic)
|
||||
tl.to('[data-composition-id="intro"] .title-card', {
|
||||
x: '-120%',
|
||||
opacity: 0,
|
||||
duration: 0.6,
|
||||
ease: 'power2.in'
|
||||
}, 1.8);
|
||||
|
||||
window.__timelines["intro"] = tl;
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,58 @@
|
||||
# HyperFrames Design Review
|
||||
|
||||
## First Impression
|
||||
This looks like a "warm and cozy" template that's trying too hard to be organic and ends up feeling like a corporate HR presentation from 2018. The "grain" is a lazy attempt at personality, and the layout is a chaotic mess of shifting boxes that lack any real editorial rhythm.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL Design Failures
|
||||
|
||||
### The "Drunken" A-Roll Framing
|
||||
**Where:** `index.html` (GSAP timeline)
|
||||
**What's wrong:** The video is constantly shifting, scaling, and sliding around the screen like it's trying to escape the viewer. At one point it's at `x: 1100`, then `x: 600`, then `y: 750`, then `x: 1320`.
|
||||
**Why it matters:** It’s physically nauseating. Instead of creating a dynamic layout, you've created a composition that feels unstable. The viewer's eyes never know where to settle because the primary subject (the speaker) is in a state of perpetual motion for no reason.
|
||||
**Fix it:** Pick two or three solid "anchor" layouts (e.g., Left-Third, Right-Third, Center-Full) and snap between them with intent. Stop the slow, aimless drifting.
|
||||
|
||||
### The "Safety First" Color Palette
|
||||
**Where:** `compositions/intro.html`, `compositions/graphics.html`
|
||||
**What's wrong:** Forest Green (`#3B5E3A`), Ochre (`#CC8832`), and Terracotta (`#C45D3E`) on a Cream background. It’s the "Earth Tones Starter Pack." It’s safe, it’s boring, and it has zero energy for a video about "Editor Agents" and "Motion Graphics."
|
||||
**Why it matters:** There is no visual "pop." The colors are so muted they bleed into each other, making the graphics feel heavy and dated rather than modern and sharp.
|
||||
**Fix it:** Introduce a high-contrast accent color or lean harder into a monochromatic sophisticated look. Right now, it looks like a brochure for a national park.
|
||||
|
||||
### Caption Box Suffocation
|
||||
**Where:** `compositions/captions.html`
|
||||
**What's wrong:** The caption box has a fixed `padding: 12px 32px` and a `min-width: 100px`, but the text inside is a massive `48px` bold Outfit. When the text gets long, it’s going to hit the edges of that box and look like it's trying to burst out.
|
||||
**Why it matters:** It lacks "breathing room." Good typography needs negative space to feel premium. This feels cramped and amateur.
|
||||
**Fix it:** Increase the padding significantly or use a dynamic width that scales better with the text. Also, consider a semi-transparent background instead of that solid muddy brown (`#7A6248`).
|
||||
|
||||
---
|
||||
|
||||
## Design Improvements
|
||||
|
||||
### The "Grain" is a Lie
|
||||
**Where:** `index.html` (`.grain-texture`)
|
||||
**The problem:** You’re using a static PNG pattern (`natural-paper.png`) and jittering it at `0.15` opacity. It doesn't look like film grain; it looks like the screen is dirty.
|
||||
**Make it better:** Use a real SVG noise filter or a higher-frequency noise. If you want "Warm Grain," make it feel like a texture, not a CSS glitch.
|
||||
|
||||
### Robotic Graphic Entrances
|
||||
**Where:** `compositions/graphics.html`
|
||||
**The problem:** Every single graphic uses the exact same `back.out(1.7)` ease. It’s the "My First GSAP Animation" preset.
|
||||
**Make it better:** Vary the entrance logic. Maybe the pill slides in, the circle expands from the center, and the rectangle fades with a staggered text reveal. Giving everything the same "bounce" makes the whole video feel repetitive by the 5-second mark.
|
||||
|
||||
---
|
||||
|
||||
## What Actually Works
|
||||
|
||||
### Typography Choice
|
||||
The use of **Outfit** is actually a decent choice. It’s a clean, geometric sans-serif that bridges the gap between "tech" and "friendly." The weights are used correctly to create some semblance of hierarchy in the intro, even if the rest of the design fails it.
|
||||
|
||||
---
|
||||
|
||||
## Design Verdict
|
||||
|
||||
**Visual Impact:** 3/10 - It’s forgettable. It looks like a template you’d find on page 4 of a stock site.
|
||||
**Color & Typography:** 5/10 - Typography is fine; the color palette is a snooze-fest.
|
||||
**Motion & Animation Feel:** 2/10 - The A-roll movement is erratic and the graphic eases are generic.
|
||||
**Overall Aesthetic:** 3/10 - "Corporate Organic" is a hard look to pull off, and this doesn't do it.
|
||||
|
||||
**Bottom Line:** I wouldn't show this to a client unless I wanted them to fall asleep or get motion sickness. It needs a complete overhaul of the layout strategy and a much bolder approach to color.
|
||||
@@ -0,0 +1,195 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Warm Grain - Editor Agent</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;
|
||||
overflow: hidden;
|
||||
background-color: #F5F0E0; /* Cream background */
|
||||
font-family: 'Outfit', 'Lexend', sans-serif;
|
||||
}
|
||||
|
||||
#main-composition {
|
||||
position: relative;
|
||||
width: 1920px;
|
||||
height: 1080px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Paper texture and Grain overlay */
|
||||
#grain-overlay-comp {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Grain animation */
|
||||
@keyframes grain-noise {
|
||||
0%, 100% { transform: translate(0, 0); }
|
||||
10% { transform: translate(-5%, -5%); }
|
||||
20% { transform: translate(-10%, 5%); }
|
||||
30% { transform: translate(5%, -10%); }
|
||||
40% { transform: translate(-5%, 15%); }
|
||||
50% { transform: translate(-10%, 5%); }
|
||||
60% { transform: translate(15%, 0); }
|
||||
70% { transform: translate(0, 10%); }
|
||||
80% { transform: translate(-15%, 0); }
|
||||
90% { transform: translate(10%, 5%); }
|
||||
}
|
||||
|
||||
.grain-texture {
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: url('https://www.transparenttextures.com/patterns/natural-paper.png');
|
||||
opacity: 0.15;
|
||||
animation: grain-noise 0.5s steps(1) infinite;
|
||||
}
|
||||
|
||||
#a-roll {
|
||||
position: absolute;
|
||||
border-radius: 16px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.comp-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-composition" data-composition-id="main-video" data-width="1920" data-height="1080" data-duration="17">
|
||||
|
||||
<!-- Background Layer -->
|
||||
<div id="grain-overlay-comp" data-composition-id="grain-overlay" data-width="1920" data-height="1080" data-duration="17" data-track-index="100">
|
||||
<div class="grain-texture"></div>
|
||||
<script>
|
||||
const grainTl = gsap.timeline({ paused: true });
|
||||
window.__timelines["grain-overlay"] = grainTl;
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<!-- A-Roll Video -->
|
||||
<video id="a-roll"
|
||||
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/2111e840_0579531a2ffe4c3ea8295b42051fb394.mp4"
|
||||
data-start="0"
|
||||
data-duration="17"
|
||||
data-track-index="0">
|
||||
</video>
|
||||
|
||||
<!-- Compositions -->
|
||||
<div id="intro-layer" class="comp-layer"
|
||||
data-composition-id="intro"
|
||||
data-composition-src="compositions/intro.html"
|
||||
data-start="0"
|
||||
data-duration="2.5"
|
||||
data-track-index="1">
|
||||
</div>
|
||||
|
||||
<div id="graphics-layer" class="comp-layer"
|
||||
data-composition-id="graphics"
|
||||
data-composition-src="compositions/graphics.html"
|
||||
data-start="0"
|
||||
data-duration="17"
|
||||
data-track-index="2">
|
||||
</div>
|
||||
|
||||
<div id="captions-layer" class="comp-layer"
|
||||
data-composition-id="captions"
|
||||
data-composition-src="compositions/captions.html"
|
||||
data-start="0"
|
||||
data-duration="17"
|
||||
data-track-index="3">
|
||||
</div>
|
||||
|
||||
<!-- SFX -->
|
||||
<audio id="sfx-swipe-1" src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/generated_assets/3af40f3b_386326216dcf4956984e4c1c25663cdd.mp3" data-start="1.8" data-duration="1" data-track-index="4"></audio>
|
||||
<audio id="sfx-swipe-2" src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/generated_assets/3af40f3b_386326216dcf4956984e4c1c25663cdd.mp3" data-start="4.6" data-duration="1" data-track-index="4"></audio>
|
||||
<audio id="sfx-swipe-3" src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/generated_assets/3af40f3b_386326216dcf4956984e4c1c25663cdd.mp3" data-start="8.8" data-duration="1" data-track-index="4"></audio>
|
||||
|
||||
<script>
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// A-Roll Framing Strategy
|
||||
// 1. 0s-1.8s: Intro scale up from small (60%) to medium (80%) - Shifted right slightly to clear speaker card
|
||||
// 2. 1.8s-4.6s: Grid view / Shift Left (x: 600, scale: 70%) for 47% graphic
|
||||
// 3. 4.6s-8.8s: Shift Center-Bottom (y: 800, scale: 70%) for 62% graphic (top)
|
||||
// 4. 8.8s-14s: Shift Right (x: 1320, scale: 75%) for Editing Skills graphic (left)
|
||||
// 5. 14s-17s: Final Reveal (Center, scale: 100%)
|
||||
|
||||
// Initial state
|
||||
gsap.set('#a-roll', { x: 1100, y: 540, xPercent: -50, yPercent: -50, width: 1920 * 0.6, height: 1080 * 0.6 });
|
||||
|
||||
// 1. Intro Scale Up (0s - 1.8s)
|
||||
tl.to('#a-roll', {
|
||||
x: 1200,
|
||||
width: 1920 * 0.8,
|
||||
height: 1080 * 0.8,
|
||||
duration: 1.8,
|
||||
ease: "power2.inOut"
|
||||
}, 0);
|
||||
|
||||
// 2. Moment 1: 47% Graphic Trigger (1.8s) - Shift Left
|
||||
tl.to('#a-roll', {
|
||||
x: 600,
|
||||
y: 540,
|
||||
width: 1920 * 0.7,
|
||||
height: 1080 * 0.7,
|
||||
duration: 0.5,
|
||||
ease: "power2.inOut"
|
||||
}, 1.8);
|
||||
|
||||
// 3. Moment 2: 62% Graphic Trigger (4.6s) - Shift Center-Bottom
|
||||
tl.to('#a-roll', {
|
||||
x: 960,
|
||||
y: 750,
|
||||
width: 1920 * 0.7,
|
||||
height: 1080 * 0.7,
|
||||
duration: 0.5,
|
||||
ease: "power2.inOut"
|
||||
}, 4.6);
|
||||
|
||||
// 4. Moment 3: Editing Skills Trigger (8.8s) - Shift Right
|
||||
tl.to('#a-roll', {
|
||||
x: 1320,
|
||||
y: 540,
|
||||
width: 1920 * 0.75,
|
||||
height: 1080 * 0.75,
|
||||
duration: 0.5,
|
||||
ease: "power2.inOut"
|
||||
}, 8.8);
|
||||
|
||||
// 5. Final Reveal (14s - 17s)
|
||||
tl.to('#a-roll', {
|
||||
x: 960,
|
||||
y: 540,
|
||||
width: 1920 * 1.0,
|
||||
height: 1080 * 1.0,
|
||||
duration: 1.0,
|
||||
ease: "power2.inOut"
|
||||
}, 14);
|
||||
|
||||
window.__timelines["main-video"] = tl;
|
||||
</script>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user