From dcf54cad8529526fd7f8d9d4b84b63ccb3fa9630 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 5 Apr 2026 08:40:09 +0200 Subject: feat: add JavaScript modules (theme toggle, matrix rain, progress tracking, copy-to-clipboard) Implement all 4 JavaScript modules: - theme-toggle.js: Theme switching with localStorage persistence - matrix-rain.js: Animated matrix-style rain effect on canvas - progress-bar.js: Reading progress tracking during scroll - copy-code.js: Copy-to-clipboard functionality for code blocks Co-Authored-By: Claude Haiku 4.5 --- assets/js/matrix-rain.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 assets/js/matrix-rain.js (limited to 'assets/js/matrix-rain.js') diff --git a/assets/js/matrix-rain.js b/assets/js/matrix-rain.js new file mode 100644 index 0000000..479231f --- /dev/null +++ b/assets/js/matrix-rain.js @@ -0,0 +1,65 @@ +// 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 + let cols, drops, raf; + + function init() { + canvas.width = canvas.offsetWidth; + canvas.height = canvas.offsetHeight; + 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 }; +})(); -- cgit v1.2.3