Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,31 @@ assets/ref-voice.wav
*.bak
skills/argo-guide-workspace/
videos/clips/

# Hyperframes-installed blocks — install via `npx hyperframes add <block>` per
# project (see demos/composition-trio.demo.ts for the install command). Blocks
# are version-pinned by hyperframes' CLI; tracking them here would fork the
# upstream artifacts.
models/
compositions/apple-*
compositions/blue-sweater-*
compositions/vfx-*
compositions/experimental-*
compositions/components/
assets/joe-sai-avatar.png
assets/sfx-production.wav
assets/sfx/

# Hyperframes-installed AI agent skills (`hyperframes skills` install).
# These are version-pinned by their CLI and would fork the upstream.
# Argo's own argo-guide skill stays tracked in skills/argo-guide and
# .agents/skills/argo-guide (symlinks).
.agents/skills/*
!.agents/skills/argo-guide
.claude/skills/
skills/*
!skills/argo-guide
skills-lock.json

# Hyperframes website-capture output — regenerable via `hyperframes capture`.
argo-showcase-captured/
48 changes: 48 additions & 0 deletions compositions/argo-launch-intro.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Argo launch intro</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; background: #0f172a; overflow: hidden; }
body { display: grid; place-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; color: #fff; }
.stage { position: relative; width: 100%; height: 100%; display: grid; place-items: center; gap: 28px; grid-template-rows: auto auto auto; align-content: center; padding: 0 80px; box-sizing: border-box; }
.eyebrow { font-size: 22px; letter-spacing: 0.32em; opacity: 0.55; text-transform: uppercase; font-weight: 600; }
.title { font-size: 184px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #f472b6 100%); -webkit-background-clip: text; background-clip: text; color: transparent; text-align: center; }
.subtitle { font-size: 36px; opacity: 0.75; letter-spacing: 0.01em; font-weight: 400; }
.accent-l { position: absolute; top: 96px; left: 96px; width: 16px; height: 16px; border-radius: 50%; background: #60a5fa; box-shadow: 0 0 60px rgba(96, 165, 250, 0.6); }
.accent-r { position: absolute; bottom: 96px; right: 96px; width: 16px; height: 16px; border-radius: 50%; background: #f472b6; box-shadow: 0 0 60px rgba(244, 114, 182, 0.6); }
</style>
</head>
<body>
<div data-composition-id="argo-launch-intro" data-width="1920" data-height="1080" data-duration="4">
<div class="stage">
<div class="accent-l"></div>
<div class="accent-r"></div>
<div class="eyebrow">Now demoing</div>
<div class="title">Argo</div>
<div class="subtitle">Playwright in. Launch assets out.</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
// Paused master timeline — Argo's renderComposition resumes it after
// marking the scene. Determinism: no Math.random / Date.now / rAF, all
// motion is a pure function of timeline.time().
const tl = gsap.timeline({ paused: true });
tl.from('.accent-l', { scale: 0, opacity: 0, duration: 0.6, ease: 'back.out(2.2)' }, 0);
tl.from('.accent-r', { scale: 0, opacity: 0, duration: 0.6, ease: 'back.out(2.2)' }, 0.1);
tl.from('.eyebrow', { y: 24, opacity: 0, duration: 0.5, ease: 'power2.out' }, 0.2);
tl.from('.title', { y: 100, opacity: 0, duration: 0.9, ease: 'expo.out' }, 0.3);
tl.from('.subtitle', { y: 30, opacity: 0, duration: 0.7, ease: 'power2.out' }, 0.7);
tl.to('.title', { letterSpacing: '-0.025em', duration: 1.6, ease: 'sine.inOut' }, 1.2);
tl.to(['.accent-l', '.accent-r'], { boxShadow: '0 0 100px rgba(255,255,255,0.4)', duration: 1.2, ease: 'sine.inOut', stagger: 0.1 }, 1.4);
tl.to(['.eyebrow', '.title', '.subtitle', '.accent-l', '.accent-r'], { opacity: 0, y: -16, duration: 0.5, ease: 'power2.in', stagger: 0.04 }, 3.4);

window.__timelines = window.__timelines || {};
window.__timelines['argo-launch-intro'] = tl;
window.__compositionReady = Promise.resolve();
</script>
</body>
</html>
41 changes: 41 additions & 0 deletions compositions/argo-launch-outro.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Argo launch outro</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; background: #0f172a; overflow: hidden; }
body { display: grid; place-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; color: #fff; }
.stage { position: relative; width: 100%; height: 100%; display: grid; place-items: center; gap: 32px; grid-template-rows: auto auto auto; align-content: center; }
.logo { font-size: 144px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; background: linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #f472b6 100%); -webkit-background-clip: text; background-clip: text; color: transparent; }
.tagline { font-size: 28px; opacity: 0.65; font-weight: 400; }
.url-pill { padding: 14px 28px; border: 1.5px solid rgba(255,255,255,0.18); border-radius: 999px; font-size: 22px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: rgba(255,255,255,0.85); letter-spacing: 0.02em; }
.ring { position: absolute; width: 880px; height: 880px; border-radius: 50%; border: 1px solid rgba(96, 165, 250, 0.18); }
</style>
</head>
<body>
<div data-composition-id="argo-launch-outro" data-width="1920" data-height="1080" data-duration="3">
<div class="stage">
<div class="ring"></div>
<div class="logo">Argo</div>
<div class="tagline">Demos as code.</div>
<div class="url-pill">npx argo init</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
const tl = gsap.timeline({ paused: true });
tl.from('.ring', { scale: 0.6, opacity: 0, duration: 1.2, ease: 'expo.out' }, 0);
tl.from('.logo', { scale: 0.85, opacity: 0, duration: 0.8, ease: 'back.out(1.6)' }, 0.15);
tl.from('.tagline', { y: 20, opacity: 0, duration: 0.6, ease: 'power2.out' }, 0.5);
tl.from('.url-pill', { y: 18, opacity: 0, duration: 0.6, ease: 'power3.out' }, 0.8);
tl.to('.ring', { rotation: 90, duration: 2.2, ease: 'sine.inOut' }, 0.5);
tl.to(['.ring', '.logo', '.tagline', '.url-pill'], { opacity: 0, duration: 0.4, ease: 'power2.in' }, 2.6);

window.__timelines = window.__timelines || {};
window.__timelines['argo-launch-outro'] = tl;
window.__compositionReady = Promise.resolve();
</script>
</body>
</html>
46 changes: 46 additions & 0 deletions compositions/intro.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Argo intro composition</title>
<style>
html, body { margin: 0; padding: 0; height: 100%; background: #0f172a; overflow: hidden; }
body { display: grid; place-items: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; color: #fff; }
/* Layout the end-state: title centered, subtitle below, accent dot top-right.
GSAP animates FROM offscreen/invisible to these positions. */
.stage { position: relative; width: 100%; height: 100%; display: grid; place-items: center; gap: 24px; grid-template-rows: auto auto; align-content: center; }
.title { font-size: 144px; font-weight: 800; letter-spacing: -0.04em; line-height: 1; background: linear-gradient(135deg, #60a5fa, #a78bfa, #f472b6); -webkit-background-clip: text; background-clip: text; color: transparent; }
.subtitle { font-size: 32px; opacity: 0.7; letter-spacing: 0.02em; font-weight: 400; }
.accent { position: absolute; top: 80px; right: 80px; width: 24px; height: 24px; border-radius: 50%; background: #f472b6; box-shadow: 0 0 80px rgba(244, 114, 182, 0.6); }
</style>
</head>
<body>
<div data-composition-id="intro" data-width="1920" data-height="1080" data-duration="3.5">
<div class="stage">
<div class="accent"></div>
<div class="title">Argo</div>
<div class="subtitle">Demos as code.</div>
</div>
</div>

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script>
// Composition contract: build a paused master timeline that argo's
// renderComposition resumes after marking the scene. Determinism: no
// Math.random / Date.now / requestAnimationFrame — all motion is pure
// function of timeline.time().
const tl = gsap.timeline({ paused: true });
tl.from('.accent', { scale: 0, opacity: 0, duration: 0.6, ease: 'back.out(2)' }, 0);
tl.from('.title', { y: 80, opacity: 0, duration: 0.9, ease: 'power3.out' }, 0.2);
tl.from('.subtitle', { y: 40, opacity: 0, duration: 0.7, ease: 'power2.out' }, 0.5);
// Hold then exit (renderComposition handles cleanup; this just animates
// the visible window before the scene ends).
tl.to('.title', { letterSpacing: '-0.02em', duration: 1.2, ease: 'sine.inOut' }, 1.0);
tl.to(['.title', '.subtitle', '.accent'], { opacity: 0, duration: 0.5, ease: 'power2.in' }, 3.0);

window.__timelines = window.__timelines || {};
window.__timelines['intro'] = tl;
window.__compositionReady = Promise.resolve();
</script>
</body>
</html>
34 changes: 34 additions & 0 deletions demos/argo-launch.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { defineConfig } from '@argo-video/cli';

// Argo × Hyperframes crossover demo (Plan A).
//
// Stable chromium + jpeg-stitch + DSF=2 (Argo's high-quality recording path).
// Compositions are GSAP-only — no Canary / html-in-canvas needed.
//
// Run:
// BASE_URL=http://127.0.0.1:8976 \
// npx argo pipeline argo-launch --config demos/argo-launch.config.mjs
export default defineConfig({
baseURL: 'http://127.0.0.1:8976',
demosDir: 'demos',
outputDir: 'videos',
tts: { defaultVoice: 'af_heart', defaultSpeed: 1.0 },
video: {
width: 1920,
height: 1080,
fps: 30,
browser: 'chromium',
captureMode: 'jpeg-stitch',
deviceScaleFactor: 2,
},
export: {
preset: 'medium',
crf: 18,
encoder: 'cpu',
transition: { type: 'fade-through-black', durationMs: 600 },
audio: { loudnorm: true },
},
overlays: {
autoBackground: true,
},
});
44 changes: 44 additions & 0 deletions demos/argo-launch.demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Argo × Hyperframes crossover demo (Plan A — tight launch teaser).
//
// Three-scene structure:
// 1. argo-launch-intro composition (4s, hand-rolled, hyperframes contract)
// 2. recorded Argo showcase hero (8s, the real running app)
// 3. argo-launch-outro composition (3s, hand-rolled)
//
// Total ~15s. Demonstrates the crossover seam: composition scenes book-end
// a real Playwright recording in a single argo pipeline invocation.
//
// Prerequisite: showcase server running —
// python3 -m http.server 8976 --directory demos
//
// Run: BASE_URL=http://127.0.0.1:8976 \
// npx argo pipeline argo-launch --config demos/argo-launch.config.mjs
import { test } from '@argo-video/cli';
import { renderComposition, spotlight, focusRing, resetCamera } from '@argo-video/cli';

test('argo-launch', async ({ page, narration }) => {
test.setTimeout(120_000);

// Scene 1 — intro composition. Starts recording itself after the
// composition is ready, so the recording's first frame is the intro's
// first animated frame.
await renderComposition(page, narration, 'compositions/argo-launch-intro.html', {
scene: 'intro',
});

// Scene 2 — recorded Argo showcase hero. The recording is already active
// from the intro composition; just navigate to the showcase and let
// CDP-direct capture the page.
await page.goto('/showcase.html');
await page.waitForTimeout(400);
narration.mark('hero');
spotlight(page, '#hero-command', { duration: 5000, padding: 18 });
focusRing(page, '#hero', { color: '#60a5fa', duration: 3500 });
await page.waitForTimeout(7600);
await resetCamera(page);

// Scene 3 — outro composition.
await renderComposition(page, narration, 'compositions/argo-launch-outro.html', {
scene: 'outro',
});
});
5 changes: 5 additions & 0 deletions demos/argo-launch.scenes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{ "scene": "intro" },
{ "scene": "hero", "text": "This is Argo. Playwright in. Launch assets out." },
{ "scene": "outro" }
]
23 changes: 23 additions & 0 deletions demos/composition-intro.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from '@argo-video/cli';

// Composition demo — renders compositions/intro.html as a single Argo scene.
// Demonstrates the renderComposition primitive without involving any
// recorded app content.
export default defineConfig({
baseURL: 'about:blank',
demosDir: 'demos',
outputDir: 'videos',
video: {
width: 1920,
height: 1080,
fps: 30,
browser: 'chromium',
captureMode: 'jpeg-stitch',
deviceScaleFactor: 2,
},
export: {
preset: 'medium',
crf: 18,
encoder: 'cpu',
},
});
17 changes: 17 additions & 0 deletions demos/composition-intro.demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Proves out renderComposition end-to-end. The demo is composed entirely of
// composition scenes — no recorded app — to validate the primitive in
// isolation. Real demos will mix recorded scenes with composition scenes
// (intros, outros, device frames around the recording).
import { test } from '@argo-video/cli';
import { renderComposition } from '@argo-video/cli';

test('composition-intro', async ({ page, narration }) => {
test.setTimeout(30_000);

await narration.startRecording(page);

await renderComposition(page, narration, 'compositions/intro.html', {
scene: 'intro',
// durationMs omitted — composition's data-duration (3.5s) is honored.
});
});
3 changes: 3 additions & 0 deletions demos/composition-intro.scenes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{ "scene": "intro" }
]
27 changes: 27 additions & 0 deletions demos/composition-iphone.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { defineConfig } from '@argo-video/cli';

// Render an imported hyperframes block (vfx-iphone-device) as an Argo
// composition scene. The block uses html-in-canvas (CanvasDrawElement),
// which is currently Canary-only behind a flag.
//
// Run: npx argo pipeline composition-iphone --config demos/composition-iphone.config.mjs
export default defineConfig({
baseURL: 'about:blank',
demosDir: 'demos',
outputDir: 'videos',
video: {
width: 1920,
height: 1080,
fps: 30,
browser: 'chromium',
captureMode: 'jpeg-stitch',
deviceScaleFactor: 2,
experimentalCanvasDrawElement: true,
browserChannel: 'chrome-canary',
},
export: {
preset: 'medium',
crf: 18,
encoder: 'cpu',
},
});
50 changes: 50 additions & 0 deletions demos/composition-iphone.demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Imports a hyperframes block via `npx hyperframes add vfx-iphone-device`
// and renders it as an Argo composition scene. Proves the A+B crossover
// shapes — shared composition contract and renderComposition embedding —
// against a real hyperframes block (not a hand-rolled sample).
//
// Block install (one-time, requires Node 22+ for the hyperframes CLI):
// PATH="/opt/homebrew/opt/node@22/bin:$PATH" \
// npx hyperframes add vfx-iphone-device
//
// Drops `compositions/vfx-iphone-device.html` plus `models/{iphone,macbook}.glb`
// + texture PNGs into the project. These are gitignored — install per checkout.
import { writeFileSync, appendFileSync } from 'node:fs';
import { test } from '@argo-video/cli';
import { renderComposition } from '@argo-video/cli';

// Browser-side logs and errors are buffered by record.ts and only surfaced
// on a Playwright failure, so write them directly to disk for diagnosis
// during iteration. Tail with: tail -f /tmp/comp-iphone-browser.log
const LOG = '/tmp/comp-iphone-browser.log';
writeFileSync(LOG, '');

test('composition-iphone', async ({ page, narration }) => {
test.setTimeout(60_000);

// Forward all page console messages + errors to /tmp/comp-iphone-browser.log
// since record.ts buffers stdout/stderr and only surfaces it on Playwright failure.
page.on('console', (msg) => appendFileSync(LOG, `[${msg.type()}] ${msg.text()}\n`));
page.on('pageerror', (err) => appendFileSync(LOG, `[pageerror] ${err.message}\n${err.stack ?? ''}\n`));
page.on('requestfailed', (req) => appendFileSync(LOG, `[reqfail] ${req.url()} ${req.failure()?.errorText ?? ''}\n`));
page.on('response', (resp) => {
if (!resp.ok()) appendFileSync(LOG, `[http ${resp.status()}] ${resp.url()}\n`);
});

// Don't call narration.startRecording here — renderComposition starts the
// recording itself AFTER the composition's warmup (page load + DRACO +
// GLTF + Three.js init), so the captured video doesn't include 5-10s of
// black setup time. Mixed demos that have recorded app scenes BEFORE the
// first composition should still call startRecording themselves.

// Block uses html-in-canvas (CanvasDrawElement). Requires
// experimentalCanvasDrawElement: true + browserChannel: 'chrome-canary'
// in the config. Block's data-duration is 15s.
await renderComposition(page, narration, 'compositions/vfx-iphone-device.html', {
scene: 'iphone-showcase',
// ready signal mismatch: hyperframes block uses internal paintReady
// flag rather than window.__compositionReady — we wait through the
// readyTimeoutMs and rely on data-duration to size the scene window.
readyTimeoutMs: 12_000,
});
});
3 changes: 3 additions & 0 deletions demos/composition-iphone.scenes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
{ "scene": "iphone-showcase" }
]
Loading
Loading