diff --git a/frontend/packages/dashboard/src/home.css b/frontend/packages/dashboard/src/home.css index 946ec551..7649eb6c 100644 --- a/frontend/packages/dashboard/src/home.css +++ b/frontend/packages/dashboard/src/home.css @@ -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; diff --git a/frontend/packages/dashboard/src/home.js b/frontend/packages/dashboard/src/home.js index 2e216ce0..944c6d1d 100644 --- a/frontend/packages/dashboard/src/home.js +++ b/frontend/packages/dashboard/src/home.js @@ -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(); diff --git a/frontend/packages/dashboard/src/index.html b/frontend/packages/dashboard/src/index.html index 2c7ad188..4fa895b2 100644 --- a/frontend/packages/dashboard/src/index.html +++ b/frontend/packages/dashboard/src/index.html @@ -12,6 +12,13 @@
+ + +