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:
Vance Ingalls
2026-03-21 22:43:56 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 10621e7903
commit 9f8e5ba5a1
401 changed files with 54545 additions and 2 deletions
@@ -0,0 +1,50 @@
# HyperFrame Schema Compliance Review
## Executive Summary
- Total files reviewed: 4
- Critical issues: 0
- Overall compliance status: PASS
## Critical Issues
None. The compositions follow the HyperFrame schema correctly, including deterministic logic, finite timelines, and proper registration.
## 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 `<img>` clips 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
### [index.html]
**Status**: COMPLIANT
**Issues Found**:
- None. The root composition correctly orchestrates sub-compositions and the A-roll video.
### [compositions/bg-grid.html]
**Status**: COMPLIANT
**Issues Found**:
- None. The script uses deterministic loops and fixed arrays for cube placement. The timeline is registered correctly.
### [compositions/mg-overlays.html]
**Status**: COMPLIANT
**Issues Found**:
- None. The script uses a finite loop for the background grid pulse instead of an infinite repeat, ensuring a finite timeline.
### [compositions/captions.html]
**Status**: COMPLIANT
**Issues Found**:
- None. Captions are grouped and animated deterministically based on the transcript.
@@ -0,0 +1,243 @@
<template id="bg-grid-template">
<div data-composition-id="bg-grid-comp" data-width="1080" data-height="1920" data-duration="13.88">
<div class="grid-container" id="grid-container"></div>
<div class="wash-container" id="wash-container"></div>
<div class="cubes-container" id="cubes-container"></div>
<style>
[data-composition-id="bg-grid-comp"] {
width: 1080px;
height: 1920px;
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0) 24%),
linear-gradient(180deg, #F7F2E9 0%, #EFE8DD 100%);
position: relative;
overflow: hidden;
}
[data-composition-id="bg-grid-comp"] .grid-container {
position: absolute;
inset: 0;
z-index: 1;
}
[data-composition-id="bg-grid-comp"] .wash-container {
position: absolute;
inset: 0;
z-index: 0;
}
[data-composition-id="bg-grid-comp"] .wash-band {
position: absolute;
opacity: 0;
mix-blend-mode: multiply;
}
[data-composition-id="bg-grid-comp"] .grid-line {
position: absolute;
background-color: #1E1E1E;
opacity: 0;
}
[data-composition-id="bg-grid-comp"] .grid-line.horizontal {
height: 1px;
width: 100%;
}
[data-composition-id="bg-grid-comp"] .grid-line.vertical {
width: 1px;
height: 100%;
}
[data-composition-id="bg-grid-comp"] .cubes-container {
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
}
[data-composition-id="bg-grid-comp"] .cube-cell {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
[data-composition-id="bg-grid-comp"] .cube-svg {
width: 74%;
height: 74%;
overflow: visible;
}
[data-composition-id="bg-grid-comp"] .cube-path {
fill: none;
stroke: #1E1E1E;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
[data-composition-id="bg-grid-comp"] .cube-face {
opacity: 0;
}
</style>
<script>
(function() {
const compId = "bg-grid-comp";
const width = 1080;
const height = 1920;
const cols = 10;
const rows = 18;
const cellW = width / cols;
const cellH = height / rows;
const container = document.getElementById('grid-container');
const washContainer = document.getElementById('wash-container');
const cubesContainer = document.getElementById('cubes-container');
const tl = gsap.timeline({ paused: true });
const washBands = [
{ x: 0, y: 213.33, w: 324, h: 320, color: 'rgba(204, 51, 51, 0.08)' },
{ x: 648, y: 0, w: 432, h: 426.67, color: 'rgba(51, 119, 187, 0.08)' },
{ x: 756, y: 1280, w: 324, h: 320, color: 'rgba(51, 119, 187, 0.05)' },
{ x: 0, y: 1493.33, w: 432, h: 426.67, color: 'rgba(30, 30, 30, 0.05)' }
];
washBands.forEach((band, index) => {
const el = document.createElement('div');
el.className = 'wash-band';
el.style.left = band.x + 'px';
el.style.top = band.y + 'px';
el.style.width = band.w + 'px';
el.style.height = band.h + 'px';
el.style.background = band.color;
washContainer.appendChild(el);
tl.to(el, {
opacity: 1,
duration: 0.7,
ease: "power2.out"
}, 0.2 + (index * 0.28));
});
for (let i = 0; i <= rows; i++) {
const line = document.createElement('div');
line.className = 'grid-line horizontal';
line.style.top = (i * cellH) + 'px';
container.appendChild(line);
tl.to(line, {
opacity: 0.24,
duration: 0.7,
ease: "expo.out"
}, (i / rows) * 0.48);
}
for (let j = 0; j <= cols; j++) {
const line = document.createElement('div');
line.className = 'grid-line vertical';
line.style.left = (j * cellW) + 'px';
container.appendChild(line);
tl.to(line, {
opacity: 0.24,
duration: 0.7,
ease: "expo.out"
}, (j / cols) * 0.48 + 0.18);
}
const cubeCells = [
{ r: 2, c: 2, color: '#3377BB' },
{ r: 4, c: 7, color: '#CC3333' },
{ r: 8, c: 3, color: '#3377BB' },
{ r: 11, c: 6, color: '#CC3333' },
{ r: 15, c: 8, color: '#3377BB' }
];
cubeCells.forEach((cell, index) => {
const cellDiv = document.createElement('div');
cellDiv.className = 'cube-cell';
cellDiv.style.width = cellW + 'px';
cellDiv.style.height = cellH + 'px';
cellDiv.style.left = (cell.c * cellW) + 'px';
cellDiv.style.top = (cell.r * cellH) + 'px';
const svg = `
<svg viewBox="0 0 100 100" class="cube-svg">
<path class="cube-face top-face" d="M50 20 L80 35 L50 50 L20 35 Z" fill="${cell.color}" />
<path class="cube-face right-face" d="M50 50 L80 35 L80 65 L50 80 Z" fill="${cell.color}" />
<path class="cube-face left-face" d="M20 35 L50 50 L50 80 L20 65 Z" fill="${cell.color}" />
<path class="cube-path outline" d="M50 20 L80 35 L80 65 L50 80 L20 65 L20 35 Z" />
<path class="cube-path inner-lines" d="M50 20 L50 50 M50 50 L80 35 M50 50 L20 35 M50 50 L50 80" />
</svg>
`;
cellDiv.innerHTML = svg;
cubesContainer.appendChild(cellDiv);
const paths = cellDiv.querySelectorAll('.cube-path');
const faces = cellDiv.querySelectorAll('.cube-face');
paths.forEach(path => {
const length = path.getTotalLength();
gsap.set(path, { strokeDasharray: length, strokeDashoffset: length });
});
const startTime = 1.45 + (index * 1.4);
paths.forEach((path, pIndex) => {
tl.to(path, {
strokeDashoffset: 0,
duration: 0.95,
ease: "power2.inOut"
}, startTime + (pIndex * 0.2));
});
faces.forEach((face, fIndex) => {
const faceTime = startTime + 0.8 + (fIndex * 0.1);
tl.to(face, {
opacity: 0.24,
duration: 0.45,
ease: "power2.out"
}, faceTime);
tl.to(face, {
opacity: 0.06,
duration: 1.2,
ease: "sine.out"
}, faceTime + 0.45);
});
tl.to(cellDiv, {
y: -12,
duration: 2.6,
ease: "sine.inOut"
}, startTime + 0.35);
tl.to(cellDiv, {
y: -4,
duration: 2.8,
ease: "sine.inOut"
}, startTime + 2.95);
});
tl.to(container, {
x: 8,
y: 8,
duration: 13.88,
ease: "none"
}, 0);
tl.to(cubesContainer, {
x: -6,
y: 4,
duration: 13.88,
ease: "none"
}, 0);
window.__timelines[compId] = tl;
})();
</script>
</div>
</template>
@@ -0,0 +1,108 @@
<template id="captions-template">
<div data-composition-id="captions-comp" data-width="1080" data-height="1920" data-duration="13.88">
<div id="captions-rail">
<div id="captions-kicker">Narration</div>
<div id="captions-container"></div>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@700;900&display=swap');
[data-composition-id="captions-comp"] {
width: 1080px;
height: 1920px;
position: relative;
pointer-events: none;
}
[data-composition-id="captions-comp"] #captions-rail {
position: absolute;
left: 108px;
bottom: 118px;
width: 864px;
min-height: 258px;
border-top: 2px solid #1E1E1E;
padding-top: 18px;
background: linear-gradient(180deg, rgba(245, 241, 234, 0) 0%, rgba(245, 241, 234, 0.88) 18%, rgba(245, 241, 234, 0.97) 100%);
}
[data-composition-id="captions-comp"] #captions-kicker {
font-family: 'IBM Plex Mono', monospace;
font-size: 18px;
font-weight: 700;
letter-spacing: 0.24em;
text-transform: uppercase;
color: rgba(30, 30, 30, 0.64);
}
[data-composition-id="captions-comp"] #captions-container {
position: relative;
margin-top: 18px;
width: 100%;
min-height: 184px;
}
[data-composition-id="captions-comp"] .caption-line {
font-family: 'IBM Plex Mono', monospace;
font-size: 56px;
font-weight: 900;
color: #1E1E1E;
opacity: 0;
position: absolute;
left: 0;
width: 96%;
line-height: 1.06;
text-transform: uppercase;
}
[data-composition-id="captions-comp"] .word {
display: inline-block;
margin: 0 6px 0 0;
}
</style>
<script>
(function() {
const container = document.getElementById('captions-container');
const tl = gsap.timeline({ paused: true });
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", start: 3.799, end: 5.639 },
{ text: "was costing you attention,", start: 5.679, end: 7.299 },
{ text: "and three out of four said you know", start: 7.319, end: 8.739 },
{ text: "the look you want but don't have", start: 8.8, end: 10.3 },
{ text: "the editing skills to get there.", start: 10.5, end: 12.239 },
{ text: "So we built Editor Agent.", start: 12.3, end: 13.84 }
];
groups.forEach(group => {
const el = document.createElement('div');
el.className = 'caption-line';
el.innerHTML = group.text.split(' ').map(word => `<span class="word">${word}</span>`).join(' ');
container.appendChild(el);
tl.fromTo(el, {
opacity: 0,
y: 26
}, {
opacity: 1,
y: 0,
duration: 0.18,
ease: "power2.out"
}, group.start);
tl.to(el, {
opacity: 0,
y: -12,
duration: 0.12,
ease: "power2.in"
}, group.end);
});
window.__timelines["captions-comp"] = tl;
})();
</script>
</div>
</template>
@@ -0,0 +1,256 @@
<template id="mg-overlays-template">
<div data-composition-id="mg-overlays-comp" data-width="1080" data-height="1920" data-duration="13.88">
<div class="overlay-container">
<div id="system-kicker">Editor Agent / Survey System</div>
<svg id="drawing-canvas" viewBox="0 0 1080 1920" preserveAspectRatio="xMidYMid slice">
<defs>
<pattern id="gridPattern" width="108" height="106.66666666666667" patternUnits="userSpaceOnUse">
<path d="M 108 0 L 0 0 0 106.66666666666667" fill="none" stroke="#1E1E1E" stroke-width="1" opacity="0.12"/>
</pattern>
</defs>
<rect class="ambient-grid" width="100%" height="100%" fill="url(#gridPattern)" />
<g id="structure-1" class="structure" transform="translate(108, 1108)">
<rect class="draw-line" x="0" y="0" width="324" height="245.33" fill="none" stroke="#CC3333" stroke-width="2" />
<rect class="fill-box red" x="0" y="0" width="324" height="245.33" fill="#CC3333" opacity="0" />
</g>
<g id="structure-2" class="structure" transform="translate(864, 214)">
<rect class="draw-line" x="-270" y="0" width="270" height="224" fill="none" stroke="#3377BB" stroke-width="2" />
<rect class="fill-box blue" x="-270" y="0" width="270" height="224" fill="#3377BB" opacity="0" />
</g>
<g id="structure-3" class="structure" transform="translate(540, 1328)">
<rect class="draw-line" x="0" y="0" width="432" height="245.33" fill="none" stroke="#1E1E1E" stroke-width="2" />
<rect class="fill-box gray" x="0" y="0" width="432" height="245.33" fill="#1E1E1E" opacity="0" />
</g>
</svg>
<div id="content-1" class="content-block" style="top: 1128px; left: 108px; width: 352px;">
<div class="eyebrow">Need motion</div>
<div class="stat-number">47%</div>
<div class="stat-label">Asked for motion graphics</div>
<div class="stat-note">Sharper pacing, clearer transitions, more visual energy.</div>
</div>
<div id="content-2" class="content-block align-right" style="top: 238px; left: 864px; width: 270px; transform: translateX(-100%);">
<div class="eyebrow">Attention drop</div>
<div class="stat-number">62%</div>
<div class="stat-label">Were stuck with static content</div>
<div class="stat-note">The message was right. The cut was not landing.</div>
</div>
<div id="content-3" class="content-block" style="top: 1348px; left: 540px; width: 432px;">
<div class="eyebrow">Execution gap</div>
<div class="stat-number">75%</div>
<div class="stat-label">Knew the look, needed the edit</div>
<div class="stat-note">The direction was clear. The hands-on workflow was the bottleneck.</div>
</div>
</div>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;700&display=swap');
[data-composition-id="mg-overlays-comp"] .overlay-container {
width: 1080px;
height: 1920px;
position: relative;
color: #1E1E1E;
font-family: 'IBM Plex Mono', monospace;
pointer-events: none;
}
[data-composition-id="mg-overlays-comp"] #system-kicker {
position: absolute;
top: 78px;
left: 108px;
z-index: 120;
font-size: 20px;
letter-spacing: 0.26em;
text-transform: uppercase;
opacity: 0;
}
[data-composition-id="mg-overlays-comp"] #drawing-canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
[data-composition-id="mg-overlays-comp"] .content-block {
position: absolute;
opacity: 0;
z-index: 100;
padding-top: 18px;
border-top: 2px solid #1E1E1E;
}
[data-composition-id="mg-overlays-comp"] .content-block.align-right {
text-align: right;
}
[data-composition-id="mg-overlays-comp"] #content-2 {
border-top-color: #3377BB;
}
[data-composition-id="mg-overlays-comp"] .eyebrow {
font-size: 18px;
text-transform: uppercase;
letter-spacing: 0.22em;
color: rgba(30, 30, 30, 0.72);
}
[data-composition-id="mg-overlays-comp"] .stat-number {
margin-top: 10px;
font-size: 132px;
font-weight: 700;
line-height: 0.92;
color: #1E1E1E;
}
[data-composition-id="mg-overlays-comp"] .stat-label {
margin-top: 16px;
font-size: 30px;
line-height: 1.2;
text-transform: uppercase;
letter-spacing: 0.08em;
}
[data-composition-id="mg-overlays-comp"] .stat-note {
margin-top: 14px;
font-size: 22px;
line-height: 1.45;
color: rgba(30, 30, 30, 0.72);
}
[data-composition-id="mg-overlays-comp"] .draw-line {
stroke-dasharray: 1400;
stroke-dashoffset: 1400;
}
[data-composition-id="mg-overlays-comp"] #content-1 .stat-number {
color: #CC3333;
}
[data-composition-id="mg-overlays-comp"] #content-2 .stat-number {
color: #3377BB;
}
[data-composition-id="mg-overlays-comp"] #content-2 .stat-label {
font-size: 24px;
}
[data-composition-id="mg-overlays-comp"] #content-2 .stat-note {
font-size: 19px;
line-height: 1.38;
}
</style>
<script>
(function() {
const tl = gsap.timeline({ paused: true });
const revealStat = ({
start,
end,
structure,
fill,
content,
fromX = 0,
fromY = 0,
}) => {
tl.to(`${structure} .draw-line`, {
strokeDashoffset: 0,
duration: 0.75,
ease: "power2.out"
}, start);
tl.to(fill, {
opacity: 0.1,
duration: 0.32,
ease: "power2.out"
}, start + 0.26);
tl.fromTo(content, {
opacity: 0,
x: fromX,
y: fromY
}, {
opacity: 1,
x: 0,
y: 0,
duration: 0.55,
ease: "power3.out"
}, start + 0.18);
tl.to([content, structure], {
opacity: 0,
y: -10,
duration: 0.28,
ease: "power2.in"
}, end - 0.28);
};
tl.fromTo("#system-kicker", {
opacity: 0,
y: -18
}, {
opacity: 1,
y: 0,
duration: 0.5,
ease: "power2.out"
}, 0.35);
revealStat({
start: 1.199,
end: 3.759,
structure: "#structure-1",
fill: "#structure-1 .fill-box.red",
content: "#content-1",
fromY: 22
});
revealStat({
start: 3.799,
end: 7.299,
structure: "#structure-2",
fill: "#structure-2 .fill-box.blue",
content: "#content-2",
fromX: 26
});
revealStat({
start: 7.319,
end: 12.239,
structure: "#structure-3",
fill: "#structure-3 .fill-box.gray",
content: "#content-3",
fromY: -22
});
tl.to(".ambient-grid", {
opacity: 0.22,
duration: 4.6,
ease: "sine.inOut"
}, 0);
tl.to(".ambient-grid", {
opacity: 0.1,
duration: 4.64,
ease: "sine.inOut"
}, 4.6);
tl.to(".ambient-grid", {
opacity: 0.2,
duration: 4.64,
ease: "sine.inOut"
}, 9.24);
window.__timelines["mg-overlays-comp"] = tl;
})();
</script>
</div>
</template>
@@ -0,0 +1,57 @@
# HyperFrames Design Review
## First Impression
This looks like a "Sol LeWitt" tribute act performed by someone who only saw a thumbnail of his work on a broken phone. Its a sterile, confused mess that tries to be "minimalist" but ends up just being "unfinished."
---
## CRITICAL Design Failures
### Typographic Identity Crisis
**Where:** `compositions/mg-overlays.html` and `compositions/captions.html`
**What's wrong:** You're mixing **IBM Plex Mono** (a technical, rigid font) for your "data" with **Inter** (the most overused, generic UI font in existence) for your captions. Its a jarring clash of "I'm a coder" and "I'm a SaaS landing page from 2019."
**Why it matters:** It destroys any sense of a cohesive visual brand. The viewer doesn't know if they're watching a technical documentary or a generic corporate explainer.
**Fix it:** Commit to the bit. Use IBM Plex Mono for *everything* to lean into the Sol LeWitt / Blueprint aesthetic, or find a high-contrast serif to actually provide some visual interest.
### The "Floating in a Void" Problem
**Where:** `compositions/mg-overlays.html`
**What's wrong:** Your stats (`47%`, `62%`, `75%`) are just... there. They have no relationship to the grid or the isometric cubes. They're just floating at arbitrary coordinates like `top: 1200px; left: 100px;`.
**Why it matters:** It looks like a mistake. In a grid-based design, *everything* must respect the grid. If it doesn't, the grid is just useless wallpaper.
**Fix it:** Align your text blocks strictly to the grid lines defined in `bg-grid.html`. Use the `cellW` and `cellH` logic to position your content so it feels structurally sound.
### Caption Background Cowardice
**Where:** `compositions/captions.html`
**What's wrong:** `background-color: rgba(0, 0, 0, 0.4);` with a `backdrop-filter: blur(4px);`. This is the "I don't know how to handle contrast" starter pack. Its ugly, its muddy, and it obscures your "beautiful" background grid.
**Why it matters:** It creates a heavy, dark blob at the bottom of an otherwise light and airy composition. Its visually exhausting.
**Fix it:** Remove the background and blur. If you need legibility, use a subtle drop shadow or, better yet, ensure your video/background doesn't compete with the text. Since your background is `#F4F4F4`, just use black text for the captions.
---
## Design Improvements
### Robotic Animation Pacing
**Where:** `compositions/bg-grid.html` and `compositions/mg-overlays.html`
**The problem:** Your animations are all using `power2.inOut` or `expo.out`. Its predictable and lacks "soul." The cubes draw themselves, then they pulse, then they float. It feels like a loading screen, not a dynamic video.
**Make it better:** Vary the easing. Use `back.out` for the stats (which you did, barely) but make it more aggressive. Give the grid lines a staggered "wipe" effect rather than just fading in. Make the motion feel like it has momentum.
### Color Palette Anemia
**Where:** `compositions/bg-grid.html`
**The problem:** `#3377BB` and `#CC3333`. These are the default "Blue" and "Red" from a 1990s Excel chart. They are boring, primary, and have zero personality.
**Make it better:** Use more sophisticated tones. Try a deeper Cobalt or a muted Terracotta. Or, go full LeWitt and use high-saturation CMYK-style colors, but give them some breathing room.
---
## What Actually Works
The **Isometric Cube SVG** construction in `bg-grid.html` is actually decent. The way you've broken down the paths for a drawing effect shows you at least understand how to build a technical asset. The `stroke-dashoffset` animation is a classic for a reason—it works. If only the rest of the design had that much thought put into it.
---
## Design Verdict
**Visual Impact:** 3/10 - Its as exciting as a graph paper notebook.
**Color & Typography:** 2/10 - A total lack of direction and courage.
**Motion & Animation Feel:** 4/10 - Functional, but lacks any rhythmic "snap."
**Overall Aesthetic:** 3/10 - "Corporate Minimalist" is just a polite way of saying "I didn't try."
**Bottom Line:** This looks like a technical demo for a library, not a finished piece of media. Its cold, its disjointed, and its boring. Fix the typography and respect your own grid, or don't bother calling it "design."
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editor Agent - Sol LeWitt 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: 1080px;
height: 1920px;
background-color: #F5F1EA;
overflow: hidden;
font-family: 'IBM Plex Mono', 'Courier New', Courier, monospace;
}
#main-canvas {
width: 1080px;
height: 1920px;
position: relative;
overflow: hidden;
background: linear-gradient(180deg, #F6F1E8 0%, #EFE8DE 100%);
}
#bg-grid-comp,
#mg-overlays-comp,
#captions-comp,
#aroll-frame {
position: absolute;
top: 0;
left: 0;
width: 1080px;
height: 1920px;
}
#bg-grid-comp {
z-index: 0;
}
#aroll-frame {
top: 188px;
left: 108px;
width: 756px;
height: 1180px;
background: #111111;
overflow: hidden;
border: 2px solid #1D1D1D;
box-shadow: 0 22px 48px rgba(17, 17, 17, 0.16);
z-index: 10;
}
#aroll-frame::before {
content: "";
position: absolute;
inset: 14px;
border: 1px solid rgba(255, 255, 255, 0.28);
pointer-events: none;
z-index: 3;
}
#aroll-video,
#aroll-frame img.__render_frame__,
#aroll-frame img.__preview_render_frame__ {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: saturate(0.88) contrast(1.06) brightness(0.97);
}
.frame-meta {
position: absolute;
left: 18px;
right: 18px;
display: flex;
justify-content: space-between;
align-items: center;
color: rgba(255, 255, 255, 0.9);
font-size: 18px;
letter-spacing: 0.18em;
text-transform: uppercase;
z-index: 4;
pointer-events: none;
}
.frame-meta.top {
top: 18px;
}
.frame-meta.bottom {
bottom: 18px;
}
#mg-overlays-comp {
z-index: 20;
}
#captions-comp {
z-index: 30;
pointer-events: none;
}
</style>
</head>
<body>
<div id="main-canvas" data-composition-id="main-comp" data-width="1080" data-height="1920" data-start="0" data-duration="13.88">
<div id="bg-grid-comp"
data-composition-id="bg-grid-comp"
data-composition-src="compositions/bg-grid.html"
data-start="0"
data-duration="13.88"
data-track-index="1">
</div>
<div id="aroll-frame">
<div class="frame-meta top">
<span>Editor Agent</span>
<span>Portrait System</span>
</div>
<video id="aroll-video"
data-start="0"
data-duration="13.88"
data-track-index="2"
src="https://gen-os-static.s3.us-east-2.amazonaws.com/astral_assets/uploaded_assets/a8bed725_8ae955c61e3141e09d9eadb97e0eef93.mp4">
</video>
<div class="frame-meta bottom">
<span>Survey Cut</span>
<span>13.88s</span>
</div>
</div>
<div id="mg-overlays-comp"
data-composition-id="mg-overlays-comp"
data-composition-src="compositions/mg-overlays.html"
data-start="0"
data-duration="13.88"
data-track-index="3">
</div>
<div id="captions-comp"
data-composition-id="captions-comp"
data-composition-src="compositions/captions.html"
data-start="0"
data-duration="13.88"
data-track-index="4">
</div>
<script>
const tl = gsap.timeline({ paused: true });
tl.fromTo('#aroll-frame', {
y: 18,
scale: 1.02
}, {
y: 0,
scale: 1,
duration: 0.9,
ease: "power2.out"
}, 0);
window.__timelines["main-comp"] = tl;
</script>
</div>
</body>
</html>