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
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 —
|
||||
|
|
|
|||
Loading…
Reference in a new issue