Some checks failed
Deploy Nexus / deploy (push) Failing after 4s
Co-authored-by: Claude (Opus 4.6) <claude@hermes.local> Co-committed-by: Claude (Opus 4.6) <claude@hermes.local>
107 lines
3.2 KiB
JavaScript
107 lines
3.2 KiB
JavaScript
/**
|
||
* matrix-rain.js — Commit-density-driven 2D canvas matrix rain
|
||
*
|
||
* Category: DATA-TETHERED AESTHETIC
|
||
* Data source: state.zoneIntensity (commit activity) + state.commitHashes
|
||
*
|
||
* Renders a Katakana/hex character rain behind the Three.js canvas.
|
||
* Density and speed are tethered to commit zone activity.
|
||
* Real commit hashes are occasionally injected as characters.
|
||
*/
|
||
|
||
const MATRIX_CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF';
|
||
const MATRIX_FONT_SIZE = 14;
|
||
|
||
let _state = null;
|
||
let _canvas = null;
|
||
let _ctx = null;
|
||
let _drops = [];
|
||
|
||
/**
|
||
* Computes mean activity [0..1] across all agent zones via state.
|
||
* @returns {number}
|
||
*/
|
||
function _totalActivity() {
|
||
if (!_state) return 0;
|
||
if (typeof _state.totalActivity === 'function') return _state.totalActivity();
|
||
const zi = _state.zoneIntensity;
|
||
if (!zi) return 0;
|
||
const vals = Object.values(zi);
|
||
return vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);
|
||
}
|
||
|
||
function _draw() {
|
||
if (!_canvas || !_ctx) return;
|
||
const activity = _totalActivity();
|
||
const commitHashes = _state?.commitHashes ?? [];
|
||
|
||
// Fade previous frame — creates the trailing glow
|
||
_ctx.fillStyle = 'rgba(0, 0, 8, 0.05)';
|
||
_ctx.fillRect(0, 0, _canvas.width, _canvas.height);
|
||
|
||
_ctx.font = `${MATRIX_FONT_SIZE}px monospace`;
|
||
|
||
const density = 0.1 + activity * 0.9;
|
||
const activeColCount = Math.max(1, Math.floor(_drops.length * density));
|
||
|
||
for (let i = 0; i < _drops.length; i++) {
|
||
if (i >= activeColCount) {
|
||
if (_drops[i] * MATRIX_FONT_SIZE > _canvas.height) continue;
|
||
}
|
||
|
||
let char;
|
||
if (commitHashes.length > 0 && Math.random() < 0.02) {
|
||
const hash = commitHashes[Math.floor(Math.random() * commitHashes.length)];
|
||
char = hash[Math.floor(Math.random() * hash.length)];
|
||
} else {
|
||
char = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
|
||
}
|
||
|
||
_ctx.fillStyle = '#aaffaa';
|
||
_ctx.fillText(char, i * MATRIX_FONT_SIZE, _drops[i] * MATRIX_FONT_SIZE);
|
||
|
||
const resetThreshold = 0.975 - activity * 0.015;
|
||
if (_drops[i] * MATRIX_FONT_SIZE > _canvas.height && Math.random() > resetThreshold) {
|
||
_drops[i] = 0;
|
||
}
|
||
_drops[i]++;
|
||
}
|
||
}
|
||
|
||
function _resetDrops() {
|
||
const colCount = Math.floor(window.innerWidth / MATRIX_FONT_SIZE);
|
||
_drops = new Array(colCount).fill(1);
|
||
}
|
||
|
||
/**
|
||
* @param {THREE.Scene} _scene (unused — 2D canvas effect)
|
||
* @param {object} state Shared state bus
|
||
* @param {object} _theme (unused — color is hardcoded green for matrix aesthetic)
|
||
*/
|
||
export function init(_scene, state, _theme) {
|
||
_state = state;
|
||
|
||
_canvas = document.createElement('canvas');
|
||
_canvas.id = 'matrix-rain';
|
||
_canvas.width = window.innerWidth;
|
||
_canvas.height = window.innerHeight;
|
||
document.body.appendChild(_canvas);
|
||
|
||
_ctx = _canvas.getContext('2d');
|
||
_resetDrops();
|
||
|
||
window.addEventListener('resize', () => {
|
||
_canvas.width = window.innerWidth;
|
||
_canvas.height = window.innerHeight;
|
||
_resetDrops();
|
||
});
|
||
|
||
// Run at ~20 fps independent of the Three.js RAF loop
|
||
setInterval(_draw, 50);
|
||
}
|
||
|
||
/**
|
||
* update() is a no-op — rain runs on its own setInterval.
|
||
*/
|
||
export function update(_elapsed, _delta) {}
|