dashboard(home): matrix-rain backdrop on the H0M3 page
Adds the falling-glyph "digital rain" effect the operator asked for as a dimmed background on the home hub. - index.html: a decorative full-viewport <canvas id="matrix-rain"> (aria-hidden, pointer-events:none) as the first body child. - home.css: fixes it behind the content (z-index:-1, inset:0) at low opacity (0.16) so the menu tiles stay legible — the "dimmed bg". - home.js: startMatrixRain() drives the classic stepped digital rain. Glyph + fade-trail colours are RESOLVED FROM THE STYLIX PALETTE at runtime (--green glyphs, --bg trail; resolved via a probe element since getComputedStyle returns custom props unresolved), so a theme swap re-colours it rather than hardcoding matrix-green. Stepped via a 55ms interval, paused while the tab is hidden, and skipped entirely under prefers-reduced-motion. CSS/JS only, home page only.
This commit is contained in:
parent
58cc720173
commit
31001d52e8
3 changed files with 92 additions and 0 deletions
|
|
@ -5,6 +5,9 @@
|
|||
// 2. fill the swarm/hive identity line when configured;
|
||||
// 3. render the shared server-warnings banner (top of every page).
|
||||
// No SSE — a portal doesn't need live updates.
|
||||
//
|
||||
// It also drives the decorative matrix-rain backdrop (#matrix-rain) —
|
||||
// see startMatrixRain() at the foot.
|
||||
|
||||
import { renderServerWarnings } from './common.js';
|
||||
|
||||
|
|
@ -48,3 +51,70 @@ async function init() {
|
|||
}
|
||||
|
||||
init();
|
||||
|
||||
// ── matrix-rain backdrop ─────────────────────────────────────────────
|
||||
// Classic falling-glyph "digital rain" on the #matrix-rain canvas, purely
|
||||
// decorative. Colours are resolved from the live stylix palette (--green
|
||||
// glyphs, --bg fade-trail) so a theme swap re-colours it; the CSS already
|
||||
// dims it + parks it behind the content. Stepped via setInterval (the
|
||||
// steppy cadence is the look and costs little CPU); paused while the tab is
|
||||
// hidden, and skipped entirely under prefers-reduced-motion.
|
||||
function startMatrixRain() {
|
||||
const canvas = $('matrix-rain');
|
||||
if (!canvas || !canvas.getContext) return;
|
||||
if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// getComputedStyle on a custom property returns its *declared* value
|
||||
// (e.g. "var(--base0B)") unresolved, so resolve to a concrete rgb() by
|
||||
// probing a real `color` computation instead.
|
||||
function resolveColor(varName, fallback) {
|
||||
const probe = document.createElement('span');
|
||||
probe.style.color = `var(${varName})`;
|
||||
probe.style.display = 'none';
|
||||
document.body.appendChild(probe);
|
||||
const c = getComputedStyle(probe).color;
|
||||
probe.remove();
|
||||
return /^rgb/.test(c) ? c : fallback;
|
||||
}
|
||||
const glyphColor = resolveColor('--green', 'rgb(166, 227, 161)');
|
||||
const fadeColor = resolveColor('--bg', 'rgb(30, 30, 46)')
|
||||
.replace(/^rgb\(/, 'rgba(').replace(/\)$/, ', 0.09)');
|
||||
|
||||
const GLYPHS = 'アイウエオカキクケコサシスセソタチツテト0123456789:=*+-<>¦';
|
||||
const CELL = 16; // glyph cell size (px)
|
||||
let cols = 0;
|
||||
let drops = [];
|
||||
|
||||
function resize() {
|
||||
canvas.width = canvas.offsetWidth;
|
||||
canvas.height = canvas.offsetHeight;
|
||||
cols = Math.max(1, Math.ceil(canvas.width / CELL));
|
||||
drops = Array.from({ length: cols }, () => Math.floor(Math.random() * -40));
|
||||
ctx.font = `${CELL}px monospace`; // canvas reset on resize clears this
|
||||
}
|
||||
|
||||
function tick() {
|
||||
// Translucent bg wash leaves a fading trail behind each glyph.
|
||||
ctx.fillStyle = fadeColor;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = glyphColor;
|
||||
for (let i = 0; i < cols; i++) {
|
||||
const ch = GLYPHS[(Math.random() * GLYPHS.length) | 0];
|
||||
ctx.fillText(ch, i * CELL, drops[i] * CELL);
|
||||
if (drops[i] * CELL > canvas.height && Math.random() > 0.975) drops[i] = 0;
|
||||
drops[i]++;
|
||||
}
|
||||
}
|
||||
|
||||
let timer = null;
|
||||
const play = () => { if (!timer) timer = setInterval(tick, 55); };
|
||||
const pause = () => { if (timer) { clearInterval(timer); timer = null; } };
|
||||
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
document.addEventListener('visibilitychange', () => (document.hidden ? pause() : play()));
|
||||
play();
|
||||
}
|
||||
|
||||
startMatrixRain();
|
||||
|
|
|
|||
Loading…
Reference in a new issue