// Embeddable replay viewer - minimal chrome for iframe embedding // §13.4: /embed/{id} - auto-play, ~50KB, Open Graph tags import { fetchReplayFromUrl } from '../lib/replay-data'; const loadReplayViewer = () => import('../replay-viewer'); export function renderEmbedPage(params: Record): void { const app = document.getElementById('app'); if (!app) return; // Parse query params const startTurn = params.start ? parseInt(params.start, 10) : 0; const speed = params.speed ? parseInt(params.speed, 10) : 4; // Default 4x speed const mode = (params.mode as 'dots' | 'voronoi' | 'influence' | 'standard') || 'dots'; // Minimal embed layout - no chrome, just the canvas app.innerHTML = `
Loading replay…
T: 0/0
`; // Show controls on hover const wrapper = app.querySelector('.canvas-wrapper') as HTMLElement; const controls = app.querySelector('.embed-controls') as HTMLElement; if (wrapper && controls) { wrapper.addEventListener('mouseenter', () => controls.style.opacity = '1'); wrapper.addEventListener('mouseleave', () => controls.style.opacity = '0'); } loadReplayViewer().then(({ ReplayViewer }) => { const replayUrl = params.id ? `/data/replays/${params.id}.json.gz` : undefined; if (!replayUrl) { const noReplay = document.getElementById('no-replay'); if (noReplay) noReplay.textContent = 'No replay specified'; return; } // Initialize viewer const canvas = document.getElementById('replay-canvas') as HTMLCanvasElement; if (!canvas) return; const viewer = new ReplayViewer(canvas, { cellSize: 16, showGrid: true, fogOfWarPlayer: null, animationSpeed: 100 / speed, // Convert speed multiplier to ms per turn viewMode: mode, showDebug: false, }); // Track play state for button toggle let isPlaying = false; // Hook into play state changes viewer.onPlayStateChange = (playing: boolean) => { isPlaying = playing; const playBtn = document.getElementById('play-btn') as HTMLButtonElement; if (playBtn) { playBtn.textContent = playing ? '⏸' : '▶'; } }; // Load replay (gzipped static asset → fetchReplayFromUrl gunzips it) fetchReplayFromUrl(replayUrl) .then((replay: any) => { viewer.loadReplay(replay); viewer.setTurn(startTurn); viewer.play(); // Update controls const resetBtn = document.getElementById('reset-btn') as HTMLButtonElement; const playBtn = document.getElementById('play-btn') as HTMLButtonElement; const turnInfo = document.getElementById('turn-info') as HTMLSpanElement; if (resetBtn) { resetBtn.disabled = false; resetBtn.onclick = () => { viewer.setTurn(0); if (!isPlaying) viewer.play(); updateTurnInfo(); }; } if (playBtn) { playBtn.disabled = false; playBtn.onclick = () => { if (isPlaying) { // No pause method available - just stop by setting turn viewer.setTurn(viewer.getTurn()); } else { viewer.play(); } }; } function updateTurnInfo() { if (turnInfo) { turnInfo.textContent = `T: ${viewer.getTurn()}/${viewer.getTotalTurns()}`; } } // Update turn info periodically setInterval(updateTurnInfo, 200); }) .catch(err => { const noReplay = document.getElementById('no-replay'); if (noReplay) noReplay.textContent = `Failed to load replay: ${err.message}`; }); }); }