mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +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,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. It’s 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. It’s 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. It’s ugly, it’s 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. It’s 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`. It’s 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 - It’s 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. It’s cold, it’s disjointed, and it’s 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>
|
||||
Reference in New Issue
Block a user