// matrix-rain.js (function () { const canvas = document.getElementById('matrix-canvas'); if (!canvas) return; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; const ctx = canvas.getContext('2d'); const CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF<>/\\|{}[]$#@!'; const FS = 14; // font size / column width in px const mode = canvas.getAttribute('data-mode') || 'background'; // 'hero' or 'background' let cols, drops, raf; function init() { if (mode === 'hero') { // Hero mode: size relative to canvas element's offsetWidth canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; } else { // Background mode: size to full viewport canvas.width = window.innerWidth; canvas.height = window.innerHeight; } cols = Math.floor(canvas.width / FS) + 1; drops = Array.from({ length: cols }, () => Math.random() * -(canvas.height / FS)); } function tick() { const light = document.documentElement.classList.contains('theme-light'); // Fade trail: near-transparent fill each frame ctx.fillStyle = light ? 'rgba(240,244,248,0.07)' : 'rgba(6,11,16,0.055)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = `${FS}px "JetBrains Mono", monospace`; for (let i = 0; i < cols; i++) { const char = CHARS[Math.floor(Math.random() * CHARS.length)]; // 4% chance of bright "head" char, otherwise use accent color ctx.fillStyle = Math.random() > 0.96 ? (light ? '#008f5a' : '#00ff88') // bright green head : (light ? '#7c3aed' : '#a855f7'); // purple trail ctx.fillText(char, i * FS, drops[i] * FS); if (drops[i] * FS > canvas.height && Math.random() > 0.975) { drops[i] = Math.random() * -20; // reset column randomly } drops[i] += 0.5; // slow fall speed } raf = requestAnimationFrame(tick); } // Listen for theme changes and reinit window.addEventListener('theme-changed', function() { // Matrix rain auto-colors based on theme-light class }, { passive: true }); init(); window.addEventListener('resize', () => { cancelAnimationFrame(raf); init(); tick(); }, { passive: true }); document.addEventListener('visibilitychange', () => { if (document.hidden) { cancelAnimationFrame(raf); } else { tick(); } }); tick(); window.MatrixRain = { init, tick }; })();