Fixes in views

This commit is contained in:
Jakub Zych
2026-04-27 14:59:24 +02:00
parent 5a56e2f8e9
commit e961d264c0
5 changed files with 207 additions and 124 deletions

View File

@@ -171,7 +171,12 @@ body {
letter-spacing: 0.12em;
}
.foot .warn { color: var(--warn); }
#rig { transition: opacity 0.4s ease; }
/* Mirrors .station .rig.glitching — bottom telemetry uses the same scramble
reveal as the top rig identifier, so it shares the warn-yellow tint. */
#rig.glitching {
color: var(--warn);
text-shadow: 0 0 10px rgba(255, 208, 0, 0.4);
}
/* ───────── Overlays ───────── */
#static {
@@ -323,55 +328,70 @@ body {
// 8 fps — visually almost identical to 12 fps but ~33% less paint work.
setInterval(drawNoise, 1000 / 8);
// ───────── Rig name in the top HUD with periodic glitch-decode ─────────
// Reads telemetry.js's window.__TEL.rig (set by telemetry.sh per-rig).
// Behaviour: settles on the real name, then every ~9s briefly scrambles
// each letter through random glyphs and resolves back left-to-right. Same
// effect family as the cyberpunk "decoder" trope; pairs with the CRT
// aesthetic rather than fighting it.
const rigNameEl = document.getElementById('rigName');
const RIG_NAME = (window.__TEL && window.__TEL.rig) || 'UNKNOWN';
// ASCII-only glyph pool — tabular_nums keeps width stable in DejaVu Mono.
// ───────── Cyberpunk decode/scramble reveal ─────────
// Each call: replace target text with random glyphs for `scrambleMs`, then
// resolve to `finalText` left-to-right. Same effect family as the cyberpunk
// "decoder" trope; pairs with the CRT aesthetic rather than fighting it.
// Used by both the top rig identifier and the bottom telemetry rotator.
// 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 upperRig = RIG_NAME.toUpperCase();
// Initial reveal: scramble for ~400ms, then resolve l→r.
// Subsequent: small idle scramble ~every 9s.
function scrambleOnce({ initial = false } = {}) {
const len = upperRig.length;
if (len === 0) { rigNameEl.textContent = '--'; return; }
rigNameEl.classList.add('glitching');
const totalScrambleMs = initial ? 600 : 360;
const stepMs = 50;
const scrambleSteps = Math.max(1, Math.floor(totalScrambleMs / stepMs));
// Tracks the in-flight scramble per element so a new call can cancel the
// previous one (otherwise back-to-back triggers double up timers).
const _scrambleHandles = new WeakMap();
function scrambleReveal(el, finalText, 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;
const scrambleTimer = setInterval(() => {
handle.s = setInterval(() => {
let s = '';
for (let i = 0; i < len; i++) s += glyph();
rigNameEl.textContent = s;
if (++step >= scrambleSteps) {
clearInterval(scrambleTimer);
// Resolve left-to-right
let resolved = 0;
const resolveTimer = setInterval(() => {
resolved++;
let s = upperRig.slice(0, resolved);
for (let i = resolved; i < len; i++) s += glyph();
rigNameEl.textContent = s;
if (resolved >= len) {
clearInterval(resolveTimer);
rigNameEl.textContent = upperRig;
rigNameEl.classList.remove('glitching');
}
}, 60);
}
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);
}
// ───────── Rig name in the top HUD ─────────
// Reads telemetry.js's window.__TEL.rig (set by telemetry.sh per-rig).
// Settles on the real name, then idle-reglitches every ~9s.
const rigNameEl = document.getElementById('rigName');
const RIG_NAME = (window.__TEL && window.__TEL.rig) || 'UNKNOWN';
const upperRig = RIG_NAME.toUpperCase();
const scrambleRig = ({ initial = false } = {}) => {
if (!upperRig.length) { rigNameEl.textContent = '--'; return; }
scrambleReveal(rigNameEl, upperRig, {
scrambleMs: initial ? 600 : 360, stepMs: 50, resolveStepMs: 60,
});
};
// First reveal happens shortly after page load; then idle re-glitch.
setTimeout(() => scrambleOnce({ initial: true }), 600);
setInterval(scrambleOnce, 9000);
setTimeout(() => scrambleRig({ initial: true }), 600);
setInterval(scrambleRig, 9000);
// ───────── Telemetry: rotating real-data subtitle ─────────
// All lines derive from telemetry.js (window.__TEL), generated by telemetry.sh.
@@ -432,24 +452,31 @@ body {
];
let lineIdx = 0;
const renderLine = () => {
// Pick the next non-null line, advancing lineIdx past dead slots.
const nextLine = () => {
for (let i = 0; i < lines.length; i++) {
const v = lines[lineIdx]();
if (v) { rigEl.textContent = v; return; }
if (v) return v;
lineIdx = (lineIdx + 1) % lines.length;
}
rigEl.textContent = '— TELEMETRY OFFLINE —';
return '— TELEMETRY OFFLINE —';
};
// Soft crossfade: 0.4s out → swap text → 0.4s in (matches the CSS transition)
const FADE_MS = 400;
// Initial paint goes in directly so the page loads with content visible;
// every subsequent transition uses the scramble reveal (same effect as the
// top rig identifier).
rigEl.textContent = nextLine();
// Bottom lines are much longer than the rig name (~2255 chars vs ~58),
// so adapt the per-char resolve step to keep total reveal under ~700ms.
const swapLine = () => {
rigEl.style.opacity = '0';
setTimeout(() => { renderLine(); rigEl.style.opacity = '1'; }, FADE_MS);
const text = nextLine();
const len = Math.max(text.length, 1);
scrambleReveal(rigEl, text, {
scrambleMs: 280,
stepMs: 45,
resolveStepMs: Math.max(15, Math.floor(700 / len)),
});
};
renderLine();
rigEl.style.opacity = '1';
setInterval(() => {
lineIdx = (lineIdx + 1) % lines.length;
swapLine();