/** * RenderPage — Headless rendering page for Puppeteer capture. * * When the app loads with ?renderMode=true, this component mounts instead * of the full editor UI. It renders a BradlyPlayer at exact pixel dimensions * and exposes window.__BRADLY_RENDER__ API for external control. * * API: * __BRADLY_RENDER__.init({ inputProps, width, height, fps, durationInFrames }) * __BRADLY_RENDER__.seekTo(frame) * __BRADLY_RENDER__.ready // boolean */ import React, { useState, useRef, useCallback, useEffect } from 'react'; import { BradlyPlayer, BradlyPlayerRef } from '../engine/player'; import { BrandComposition } from '../components/BrandComposition'; interface RenderConfig { inputProps: Record; width: number; height: number; fps: number; durationInFrames: number; } export const RenderPage: React.FC = () => { const [config, setConfig] = useState(null); const playerRef = useRef(null); const seekTo = useCallback((frame: number) => { playerRef.current?.seekTo(frame); }, []); // Expose global API for Puppeteer useEffect(() => { const api = { ready: false, init: (cfg: RenderConfig) => { setConfig(cfg); // Mark ready after React renders requestAnimationFrame(() => { requestAnimationFrame(() => { api.ready = true; }); }); }, seekTo, }; (window as any).__BRADLY_RENDER__ = api; return () => { delete (window as any).__BRADLY_RENDER__; }; }, [seekTo]); // Update ready state when config changes useEffect(() => { if (config && (window as any).__BRADLY_RENDER__) { requestAnimationFrame(() => { requestAnimationFrame(() => { (window as any).__BRADLY_RENDER__.ready = true; }); }); } }, [config]); if (!config) { return (
Waiting for render init...
); } return (
); };