diff --git a/apps/web/public/videos/hero-poster.jpg b/apps/web/public/videos/hero-poster.jpg index 58720b3..52d43fb 100644 Binary files a/apps/web/public/videos/hero-poster.jpg and b/apps/web/public/videos/hero-poster.jpg differ diff --git a/apps/web/public/videos/hero.mp4 b/apps/web/public/videos/hero.mp4 index 3e89edf..b50977c 100644 Binary files a/apps/web/public/videos/hero.mp4 and b/apps/web/public/videos/hero.mp4 differ diff --git a/apps/web/public/videos/hero.webm b/apps/web/public/videos/hero.webm index 8d97a02..2021481 100644 Binary files a/apps/web/public/videos/hero.webm and b/apps/web/public/videos/hero.webm differ diff --git a/remotion/package.json b/remotion/package.json index 02a548e..9627fcc 100644 --- a/remotion/package.json +++ b/remotion/package.json @@ -6,7 +6,7 @@ "studio": "remotion studio src/index.ts", "render:mp4": "remotion render src/index.ts HeroVideo out/hero.mp4 --codec h264 --crf 28 --pixel-format yuv420p", "render:webm": "remotion render src/index.ts HeroVideo out/hero.webm --codec vp9 --crf 32", - "render:poster": "remotion still src/index.ts HeroVideo out/hero-poster.jpg --frame 60 --image-format jpeg --jpeg-quality 85", + "render:poster": "remotion still src/index.ts HeroVideo out/hero-poster.jpg --frame 180 --image-format jpeg --jpeg-quality 85", "render:all": "pnpm render:mp4 && pnpm render:webm && pnpm render:poster", "to-web": "node scripts/publish-to-web.mjs", "build": "pnpm render:all && pnpm to-web" diff --git a/remotion/src/scenes/ServerScene.tsx b/remotion/src/scenes/ServerScene.tsx index 953c422..6f07c30 100644 --- a/remotion/src/scenes/ServerScene.tsx +++ b/remotion/src/scenes/ServerScene.tsx @@ -11,15 +11,15 @@ import { BEAT } from '../HeroVideo'; // status tag flashes briefly. Live-dot in the server's top-right corner // pulses through the whole scene. -const CX = 960; +const CX = 760; const CY = 540; -const SERVER_W = 460; -const SERVER_H = 300; +const SERVER_W = 600; +const SERVER_H = 360; // Claude client panel — anchored right of the server -const CLIENT_W = 280; -const CLIENT_H = 200; -const CLIENT_CX = 1500; +const CLIENT_W = 360; +const CLIENT_H = 240; +const CLIENT_CX = 1480; const CLIENT_CY = 540; export function ServerScene({ fps }: { fps: number }) { @@ -101,7 +101,7 @@ export function ServerScene({ fps }: { fps: number }) { {/* Tool rows inside server */} {[0, 1, 2].map((r) => { - const y = CY - 60 + r * 60; + const y = CY - 90 + r * 90; return ( - {/* Particles */} + {/* Central core — radial glow that's always-on during Beat 2. Sells + "something is building here" before the schematic is drawn. */} +
+ + {/* Particles — 60 chunky glowing dots */} + + + + + + + + + {Array.from({ length: PARTICLE_COUNT }).map((_, i) => { - // Source: prompt word approximate position spread across the line const wordIndex = i % 4; const wordX = 760 + wordIndex * 130 + rand(i * 7.13) * 60 - 30; - const wordY = 540 + rand(i * 3.71) * 20 - 10; + const wordY = 540 + rand(i * 3.71) * 24 - 12; - // Target slot on server const slot = targetSlot(i); + // Velocity vectors — particles fly outward in a roughly radial + // pattern from the prompt baseline. Magnitude varied per particle. + const angle = rand(i * 1.71) * Math.PI * 2; + const speed = 240 + rand(i * 4.13) * 380; + const vx = Math.cos(angle) * speed; + const vy = Math.sin(angle) * speed - 60; // bias slightly upward - // Scatter velocity — frames 0..25 the particle drifts outward; - // then frames 25..60 it pulls toward the target with spring. - const vx = (rand(i * 1.31) - 0.5) * 600; - const vy = (rand(i * 2.71) - 0.5) * 400; + const explode = clampLerp(local, 0, 18); + // Pull starts earlier (frame 14 instead of 25) so particles + // are visible converging rather than just drifting. + const pull = softSpring(frame, fps, BEAT.transform.in + 14, 42); - // Phase 1: explosion 0..25 - const explode = clampLerp(local, 0, 25); - // Phase 2: magnetic pull 25..60 - const pull = softSpring(frame, fps, BEAT.transform.in + 25, 36); - - // Position is: (wordPos) + (vx,vy * explode) lerped toward target by pull const driftX = wordX + vx * explode * (1 - pull); const driftY = wordY + vy * explode * (1 - pull); const x = driftX + (slot.x - driftX) * pull; const y = driftY + (slot.y - driftY) * pull; - // Size grows as particles "lock in" - const r = interpolate(pull, [0, 1], [2.5, 1.8]); - // Color shifts from white → indigo as they lock to schematic - const color = pull > 0.6 ? C.accent : C.fg; - // Fade in fast at the start so the explosion is visible + // Radius: 6→3 as particles lock in. Big enough at 1080p that + // every particle is clearly visible. + const r = interpolate(pull, [0, 1], [6, 3]); + // Always indigo — earlier two-color split was indecisive + const color = C.accent; const alpha = clampLerp(local, 0, 4); - // Slight fade-out near end as the schematic takes visual primacy - const fadeOut = 1 - clampLerp(local, 85, 105) * 0.3; + const fadeOut = 1 - clampLerp(local, 88, 108) * 0.4; return ( 0.5 ? `drop-shadow(0 0 4px ${C.accentGlow})` : undefined, - }} + filter="url(#glow)" /> ); })} - {/* Server schematic — strokes on as particles arrive */} + {/* Server schematic */} - - - + + + - {/* Outer rectangle — stroke draws perimeter from top-left clockwise */} + {/* Faint inner panel as the box draws — gives volume immediately */} - {/* Three internal "tool" rows — appear after perimeter completes */} + {/* Outer rectangle stroke */} + + + {/* Three internal tool rows */} {[0, 1, 2].map((r) => { - const rowAlpha = clampLerp(local, 60 + r * 4, 72 + r * 4); - const y = CY - 60 + r * 60; + const y = CY - 90 + r * 90; return ( - + + - ); })} - {/* Port dots — 4 each side, pulse once schematic locks in */} + {/* Port dots */} {[-1, 1].map((side) => [-1, 0, 1].map((off) => ( )), )} - {/* Scan-line sweep — diagonal pass after stroke completes */} + {/* Scan-line sweep */} {scanT > 0 && scanT < 1 && ( )} - {/* Corner labels — typographic detail that sells "this is a real server" */} - - - - + {/* Corner labels — bigger and earlier */} + + + +
); } function CornerLabel({ - x, - y, - text, - appearAt, - delay, - align = 'left', + x, y, text, appearAt, delay, align = 'left', }: { - x: number; - y: number; - text: string; - appearAt: number; - delay: number; - align?: 'left' | 'right'; + x: number; y: number; text: string; appearAt: number; delay: number; align?: 'left' | 'right'; }) { const t = clampLerp(appearAt, delay, delay + 8); return ( @@ -241,8 +256,8 @@ function CornerLabel({ right: align === 'right' ? 1920 - x : undefined, top: y, fontFamily: 'ui-monospace, SF Mono, Menlo, monospace', - fontSize: 13, - letterSpacing: '0.06em', + fontSize: 17, + letterSpacing: '0.08em', color: C.fgMuted, opacity: t, transform: `translateY(${(1 - t) * 4}px)`,