// app/components/MatrixRain.tsx 'use client'; import { useEffect, useRef, useState } from 'react'; import { useTheme } from 'next-themes'; interface MatrixRainProps { color?: string; fps?: number; } export default function MatrixRain({ color, fps = 15 }: MatrixRainProps) { const canvasRef = useRef(null); const lastWidth = useRef(0); const { resolvedTheme } = useTheme(); const [mounted, setMounted] = useState(false); useEffect(() => { const timer = setTimeout(() => setMounted(true), 0); return () => clearTimeout(timer); }, []); useEffect(() => { if (!mounted) return; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const isLight = resolvedTheme === 'light'; // Use slate-50 (#f8fafc) for light mode, #0d1117 for dark mode const bgColor = isLight ? '#f8fafc' : '#0d1117'; // The semi-transparent overlay that creates the fading trail effect const fadeColor = isLight ? 'rgba(248, 250, 252, 0.2)' : 'rgba(13, 17, 23, 0.2)'; // A darker green for light mode to maintain contrast, or the default/prop color for dark mode const textColor = color || (isLight ? '#1c2d25' : '#84c0a0'); const fontSize = 16; let columns = 0; let drops: number[] = []; const setCanvasSize = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight + 120; ctx.fillStyle = bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); }; const initDrops = () => { columns = Math.floor(canvas.width / fontSize); drops = []; for (let x = 0; x < columns; x++) { drops[x] = Math.random() * -100; } }; lastWidth.current = window.innerWidth; setCanvasSize(); initDrops(); const draw = () => { ctx.fillStyle = fadeColor; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = `bold ${fontSize}px monospace`; for (let i = 0; i < drops.length; i++) { const text = Math.random() > 0.5 ? '0' : '1'; ctx.fillStyle = textColor; ctx.fillText(text, i * fontSize, drops[i] * fontSize); if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) { drops[i] = 0; } drops[i]++; } }; const intervalId = setInterval(draw, 1000 / fps); const handleResize = () => { if (window.innerWidth !== lastWidth.current) { lastWidth.current = window.innerWidth; setCanvasSize(); initDrops(); } }; window.addEventListener('resize', handleResize); return () => { clearInterval(intervalId); window.removeEventListener('resize', handleResize); }; }, [color, fps, resolvedTheme, mounted]); // Re-run effect if theme changes return (
{mounted && ( )}
); }