/* OPHI-118 — shared HUD helpers used by every overlay. * * Exposes: * - HUD.startClock(el) — UTC ticker into the given element. * - HUD.startSignal(el, opts) — signal-bar fluctuator. * - HUD.scrambleReveal(...) — cyberpunk decode/scramble used by landing. * * Designed to no-op if the target element isn't on the page, so the * base layout can include it unconditionally. */ (function (root) { 'use strict'; const pad = n => String(n).padStart(2, '0'); function startClock(el) { if (!el) return; const tick = () => { const d = new Date(); el.textContent = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`; }; tick(); setInterval(tick, 1000); } // n thresholds default to the original landing/loading/game distribution: // r < .06 → 2 bars (occasional drop) // r < .70 → 3 bars (typical) // r < .95 → 4 bars // → 5 bars (occasional spike) function startSignal(el, opts) { if (!el) return; const profile = opts?.profile || 'normal'; const FILL = '▮', EMPTY = '▯'; const draw = n => FILL.repeat(n) + EMPTY.repeat(5 - n); setInterval(() => { const r = Math.random(); let n; if (profile === 'strong') { n = r < 0.15 ? 3 : r < 0.65 ? 4 : 5; } else if (profile === 'degrading') { n = r < 0.45 ? 1 : r < 0.85 ? 2 : r < 0.97 ? 3 : 0; } else { n = r < 0.06 ? 2 : r < 0.70 ? 3 : r < 0.95 ? 4 : 5; } el.textContent = draw(n); }, 1500); } // ASCII-only glyph pool — DejaVu Mono renders these at 1ch each, so the // intermediate frames don't shift width. const GLITCH_GLYPHS = '01ABCDEFGHIJKLMNOPQRSTUVWXYZ#@*?!§%'; const glyph = () => GLITCH_GLYPHS[(Math.random() * GLITCH_GLYPHS.length) | 0]; const _scrambleHandles = new WeakMap(); function scrambleReveal(el, finalText, opts) { opts = opts || {}; const { scrambleMs = 360, stepMs = 50, resolveStepMs = 60 } = opts; const prev = _scrambleHandles.get(el); if (prev) { clearInterval(prev.s); clearInterval(prev.r); } const len = finalText.length; if (!len) { el.textContent = ''; el.classList.remove('glitching'); _scrambleHandles.delete(el); return; } el.classList.add('glitching'); const scrambleSteps = Math.max(1, Math.floor(scrambleMs / stepMs)); const handle = { s: null, r: null }; _scrambleHandles.set(el, handle); let step = 0; handle.s = setInterval(() => { let s = ''; for (let i = 0; i < len; i++) s += glyph(); el.textContent = s; if (++step < scrambleSteps) return; clearInterval(handle.s); handle.s = null; let resolved = 0; handle.r = setInterval(() => { resolved++; let s = finalText.slice(0, resolved); for (let i = resolved; i < len; i++) s += glyph(); el.textContent = s; if (resolved < len) return; clearInterval(handle.r); handle.r = null; el.textContent = finalText; el.classList.remove('glitching'); _scrambleHandles.delete(el); }, resolveStepMs); }, stepMs); } root.HUD = { pad, startClock, startSignal, scrambleReveal }; // Auto-bind: every overlay has #clock and #signal in the shared partial. document.addEventListener('DOMContentLoaded', () => { startClock(document.getElementById('clock')); const sigEl = document.getElementById('signal'); if (sigEl) { const profile = sigEl.dataset.profile || 'normal'; startSignal(sigEl, { profile }); } }); })(typeof window !== 'undefined' ? window : globalThis);