1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
// 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 };
})();
|