diff --git a/components/import/TheaterCanvas.tsx b/components/import/TheaterCanvas.tsx index 7e60b014..6773cb01 100644 --- a/components/import/TheaterCanvas.tsx +++ b/components/import/TheaterCanvas.tsx @@ -215,12 +215,26 @@ const TheaterCanvas = forwardRef 1 - Math.pow(1 - v, 3) const byId = new Map(engine.nodes.map((n) => [n.id, n])) + // Ambient life (Living Paper): the constellation moves because it is + // alive right now, never to fake progress. Build mode gets full + // amplitude, settled reveals rest at half (the reveal is a verdict), + // reduced motion gets none (the loop is frozen anyway). Everything is + // derived from the clock inside this one rAF loop; no extra timers. + const ambient = reduced ? 0 : settled ? 0.5 : 1 + // Every 7s a quiet luminance wave travels hub -> rim over 2.6s, + // brightening the hairline rings and edges it passes (alpha only, no + // color, no geometry). Build mode only; -1 means resting. + const ripple = + ambient === 1 && now % 7000 < 2600 ? ((now % 7000) / 2600) * 340 : -1 + const rippleGlow = (radius: number, width: number) => + ripple < 0 ? 0 : Math.max(0, 1 - Math.abs(radius - ripple) / width) + for (const n of engine.nodes) { if (n.kind !== 'ring' || n.born == null) continue const p = ease(age(n)) const rad = (n.ring ?? 0) * k ctx.strokeStyle = colors.hair - ctx.globalAlpha = 0.75 * p + ctx.globalAlpha = (0.75 + 0.18 * rippleGlow(n.ring ?? 0, 30)) * p ctx.lineWidth = 0.8 ctx.beginPath() ctx.arc(cx, cy, rad, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * p) @@ -244,9 +258,11 @@ const TheaterCanvas = forwardRef