/** * Subtle animated dot grid background for RecollectionsPage. * Matches canvas grid (20px gap), with gentle wave movement. */ import React, { useCallback, useEffect, useRef } from 'react' const GAP = 20 const SPEED = 0.010 const AMPLITUDE = 4 const DOT_SIZE = 1.2 const PULSE_AMOUNT = 0.25 function getDotColor(): string { if (typeof document === 'undefined') return 'rgba(128,128,128,0.25)' const isDark = document.documentElement.classList.contains('dark') return isDark ? 'rgba(255,255,255,0.18)' : 'rgba(0,0,0,0.12)' } export function RecollectionsPageBackground({ className }: { className?: string }) { const canvasRef = useRef(null) const timeRef = useRef(0) const rafRef = useRef(0) const draw = useCallback(() => { const canvas = canvasRef.current if (!canvas) return const ctx = canvas.getContext('2d') if (!ctx) return const { width, height } = canvas const cols = Math.floor(width / GAP) + 2 const rows = Math.floor(height / GAP) + 2 const t = timeRef.current ctx.fillStyle = 'transparent' ctx.clearRect(0, 0, width, height) const dotColor = getDotColor() for (let row = 0; row < rows; row++) { for (let col = 0; col < cols; col++) { const baseX = col * GAP const baseY = row * GAP const w1 = Math.sin(t * SPEED + baseX * 0.008 + baseY * 0.006) * AMPLITUDE const w2 = Math.sin(t * SPEED * 0.7 - baseX * 0.012 + baseY * 0.01) * AMPLITUDE * 0.5 const w3 = Math.sin(t * SPEED * 1.2 + baseY * 0.015) * AMPLITUDE * 0.3 const x = baseX + w1 + w2 + w3 const y = baseY + w1 * 0.6 + w3 * 0.4 const size = DOT_SIZE + Math.sin(t * SPEED * 2 + baseX * 0.02) * PULSE_AMOUNT ctx.beginPath() ctx.arc(x, y, size, 0, Math.PI * 2) ctx.fillStyle = dotColor ctx.fill() } } timeRef.current += 1 rafRef.current = requestAnimationFrame(draw) }, []) useEffect(() => { const canvas = canvasRef.current if (!canvas) return const resize = () => { const dpr = Math.min(window.devicePixelRatio ?? 1, 2) const rect = canvas.getBoundingClientRect() canvas.width = Math.round(rect.width * dpr) canvas.height = Math.round(rect.height * dpr) canvas.style.width = `${rect.width}px` canvas.style.height = `${rect.height}px` const ctx = canvas.getContext('2d') if (ctx) ctx.setTransform(dpr, 0, 0, dpr, 0, 0) } resize() window.addEventListener('resize', resize) rafRef.current = requestAnimationFrame(draw) return () => { window.removeEventListener('resize', resize) if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, [draw]) return ( ) }