const DEFAULT_PREVIEW_PROGRESS = 0.48; function clamp01(value) { return Math.min(1, Math.max(0, Number(value) || 0)); } function representativeTime(recipe) { const phase = clamp01(recipe?.reducedMotion?.phase ?? 0.25); const cycleMs = Number(recipe?.timeline?.macroCycleMs) || Number(recipe?.timeline?.durationMs) || 4000; return (Math.max(1, cycleMs) / 1000) * phase; } /** * One-shot recipes may advance to a different state while their card is active. * Atlas previews retain the requested recipe so their identity and resting frame * always match the card label. */ export function createThumbnailRecipe(recipe) { if (!recipe?.timeline?.next) return recipe; return { ...recipe, timeline: { ...recipe.timeline, next: null, }, }; } export function shouldAnimateThumbnail( id, { selectedId = null, mainRunning = false, hoveredIds = new Set(), focusedIds = new Set(), } = {}, ) { return ( (id === selectedId && mainRunning) || hoveredIds.has(id) || focusedIds.has(id) ); } /** * Render a deterministic, authored representative phase without leaving the * preview in reduced-motion mode. JoanGlyphEngine maps time zero to each * recipe's reducedMotion.phase while reduced motion is enabled. */ export function renderThumbnailRestFrame( preview, { progress = DEFAULT_PREVIEW_PROGRESS } = {}, ) { if (!preview || preview.destroyed) return null; const nextProgress = clamp01(progress); if (Math.abs(preview.progress - nextProgress) > 1e-6) { preview.setProgress(nextProgress); } // Neighbor-propagation previews otherwise retain their previous gate buffer, // making the supposedly static pose depend on how long the card was hovered. preview.stateElapsed = representativeTime(preview.currentRecipe); preview.gates?.fill(0); preview.neighborBuffer?.fill(0); preview.dwell?.fill(0); const previousReducedMotion = preview.reducedMotion; preview.reducedMotion = true; let stats; try { stats = preview.renderOnce(0); } finally { preview.reducedMotion = previousReducedMotion; } if (preview.canvas?.dataset) preview.canvas.dataset.previewState = "ready"; return stats; }