feat: add Matrix-style falling code rain background layer
Some checks failed
CI / validate (pull_request) Failing after 15s
CI / auto-merge (pull_request) Has been skipped

Implements a 2D canvas (#matrix-rain) behind the Three.js renderer
with classic Matrix green katakana/numeral/hex characters falling
in columns. The Three.js renderer is made alpha-transparent so the
rain shows through, creating an atmospheric depth effect.

- Add matrixCanvas (id=matrix-rain) appended before the WebGL canvas
- Animate at ~20 fps with a semi-transparent fade trail (0.05 alpha)
- Bright (#aaffaa) head character, dimmed by trail accumulation
- Renderer switched to alpha:true + setClearColor(0,0) — transparent bg
- scene.background removed; body background (#000008) shows through
- CSS: #matrix-rain { z-index: 0; opacity: 0.18 } for subtle blend

Fixes #262

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexander Whitestone
2026-03-24 01:04:08 -04:00
parent b1fc67fc2f
commit 1f7e5fd6ca
2 changed files with 57 additions and 2 deletions

53
app.js
View File

@@ -38,9 +38,57 @@ textureLoader.load('placeholder-texture.jpg', (texture) => {
loadedAssets.set('placeholder-texture', texture);
});
// === MATRIX RAIN ===
// 2D canvas layer rendered behind the Three.js scene.
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() {
// Fade previous frame with semi-transparent black overlay (creates the trail)
matrixCtx.fillStyle = 'rgba(0, 0, 8, 0.05)';
matrixCtx.fillRect(0, 0, matrixCanvas.width, matrixCanvas.height);
matrixCtx.font = `${MATRIX_FONT_SIZE}px monospace`;
for (let i = 0; i < matrixDrops.length; i++) {
const char = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
const x = i * MATRIX_FONT_SIZE;
const y = matrixDrops[i] * MATRIX_FONT_SIZE;
// Head character: bright white-green
matrixCtx.fillStyle = '#aaffaa';
matrixCtx.fillText(char, x, y);
// Reset drop to top with some randomness
if (y > matrixCanvas.height && Math.random() > 0.975) {
matrixDrops[i] = 0;
}
matrixDrops[i]++;
}
}
// Animate at ~20 fps (independent of Three.js loop)
setInterval(drawMatrixRain, 50);
// Resize handler for matrix canvas
window.addEventListener('resize', () => {
matrixCanvas.width = window.innerWidth;
matrixCanvas.height = window.innerHeight;
});
// === SCENE SETUP ===
const scene = new THREE.Scene();
scene.background = new THREE.Color(NEXUS.colors.bg);
// Background is null — the matrix rain canvas shows through the transparent renderer
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
camera.position.set(0, 6, 11);
@@ -57,7 +105,8 @@ const overheadLight = new THREE.PointLight(0x8899bb, 0.6, 60);
overheadLight.position.set(0, 25, 0);
scene.add(overheadLight);
const renderer = new THREE.WebGLRenderer({ antialias: true });
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setClearColor(0x000000, 0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

View File

@@ -30,6 +30,12 @@ canvas {
left: 0;
}
/* Matrix rain sits behind the Three.js renderer */
#matrix-rain {
z-index: 0;
opacity: 0.18;
}
/* === HUD === */
.hud-controls {
z-index: 10;