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:
iris 2026-06-17 21:46:25 +02:00
commit 31001d52e8
3 changed files with 92 additions and 0 deletions

View file

@ -13,6 +13,21 @@ body.home-shell {
min-height: 100vh;
}
/* Matrix-rain backdrop (driven by home.js). Fixed, full-viewport, dimmed,
and behind every other element (z-index below the auto-stacked content).
Decorative only pointer-events off so it never intercepts clicks. The
low opacity keeps the menu tiles legible over it; glyph + fade colours
are themed from the stylix palette in JS. */
#matrix-rain {
position: fixed;
inset: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.16;
pointer-events: none;
}
.home-header {
text-align: center;
padding: 1.5em 0 0.5em;

View file

@ -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();

View file

@ -12,6 +12,13 @@
</head>
<body class="home-shell">
<!-- Decorative matrix-rain backdrop: a dimmed full-viewport canvas of
falling glyphs behind everything (home.js drives it). aria-hidden +
pointer-events:none so it's purely cosmetic; glyph/bg colours are
read from the stylix palette at runtime so a theme swap re-colours
it, and it's disabled under prefers-reduced-motion. -->
<canvas id="matrix-rain" aria-hidden="true"></canvas>
<!-- H0M3: the menu hub. A plain grid of links to every top-level
surface. This is the page served at `/` — the landing page — with
the dashboard relocated to /dashboard.html. No tabbar / SSE —