Split the monolithic 5393-line app.js into 32 focused ES modules under modules/ with a thin ~330-line orchestrator. No bundler required — runs in-browser via import maps. Module structure: core/ — scene, ticker, state, theme, audio data/ — gitea, weather, bitcoin, loaders terrain/ — stars, clouds, island effects/ — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones panels/ — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel portals/ — portal-system, commit-banners narrative/ — bookshelves, oath, chat utils/ — perlin All files pass node --check. No new dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
59 lines
2.1 KiB
JavaScript
59 lines
2.1 KiB
JavaScript
// modules/effects/matrix-rain.js — 2D canvas matrix rain overlay
|
||
import { state } from '../core/state.js';
|
||
|
||
const matrixCanvas = document.createElement('canvas');
|
||
matrixCanvas.id = 'matrix-rain';
|
||
matrixCanvas.width = window.innerWidth;
|
||
matrixCanvas.height = window.innerHeight;
|
||
document.body.appendChild(matrixCanvas);
|
||
|
||
const matrixCtx = matrixCanvas.getContext('2d');
|
||
const MATRIX_CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF';
|
||
const MATRIX_FONT_SIZE = 14;
|
||
const MATRIX_COL_COUNT = Math.floor(window.innerWidth / MATRIX_FONT_SIZE);
|
||
const matrixDrops = new Array(MATRIX_COL_COUNT).fill(1);
|
||
|
||
function drawMatrixRain() {
|
||
matrixCtx.fillStyle = 'rgba(0, 0, 8, 0.05)';
|
||
matrixCtx.fillRect(0, 0, matrixCanvas.width, matrixCanvas.height);
|
||
matrixCtx.font = `${MATRIX_FONT_SIZE}px monospace`;
|
||
|
||
const activity = state.totalActivity();
|
||
const density = 0.1 + activity * 0.9;
|
||
const activeColCount = Math.max(1, Math.floor(matrixDrops.length * density));
|
||
|
||
for (let i = 0; i < matrixDrops.length; i++) {
|
||
if (i >= activeColCount) {
|
||
if (matrixDrops[i] * MATRIX_FONT_SIZE > matrixCanvas.height) continue;
|
||
}
|
||
|
||
let char;
|
||
if (state.commitHashes.length > 0 && Math.random() < 0.02) {
|
||
const hash = state.commitHashes[Math.floor(Math.random() * state.commitHashes.length)];
|
||
char = hash[Math.floor(Math.random() * hash.length)];
|
||
} else {
|
||
char = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
|
||
}
|
||
|
||
const x = i * MATRIX_FONT_SIZE;
|
||
const y = matrixDrops[i] * MATRIX_FONT_SIZE;
|
||
|
||
matrixCtx.fillStyle = '#aaffaa';
|
||
matrixCtx.fillText(char, x, y);
|
||
|
||
const resetThreshold = 0.975 - activity * 0.015;
|
||
if (y > matrixCanvas.height && Math.random() > resetThreshold) {
|
||
matrixDrops[i] = 0;
|
||
}
|
||
matrixDrops[i]++;
|
||
}
|
||
}
|
||
|
||
export function init() {
|
||
setInterval(drawMatrixRain, 50);
|
||
window.addEventListener('resize', () => {
|
||
matrixCanvas.width = window.innerWidth;
|
||
matrixCanvas.height = window.innerHeight;
|
||
});
|
||
}
|