Landing: telemetry collector, doomguard watermark, SIGNAL centering
- telemetry.sh: bash collector for static device + OBS profile specs. Default mode wraps telemetry.json → telemetry.js (script-tag-loaded global, dodging file:// fetch CORS in CEF). --collect re-reads hardware + OBS basic.ini / streamEncoder.json. - index.html: footer subtitle now rotates real CPU/MEM/GPU/OBS data with soft 400ms crossfade; SIGNAL group re-centered via 3-col grid; doomguard watermark behind everything with periodic "tune-in" spikes through the noise canvas. - telemetry.json: hand-edited cosmetic version of collected specs. - .gitignore: track telemetry.json (source), ignore telemetry.js (build artifact) and *.manual backups.
This commit is contained in:
@@ -36,8 +36,8 @@ body {
|
||||
|
||||
/* ───────── HUD top strip ───────── */
|
||||
.hud {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
font-family: var(--mono);
|
||||
font-size: 22px;
|
||||
@@ -46,6 +46,9 @@ body {
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.hud .group { display: flex; gap: 28px; align-items: center; }
|
||||
.hud > .group:nth-child(1) { justify-self: start; }
|
||||
.hud > .group:nth-child(2) { justify-self: center; }
|
||||
.hud > .group:nth-child(3) { justify-self: end; }
|
||||
.hud .dim { color: var(--hud-dim); }
|
||||
.hud .led {
|
||||
display: inline-block;
|
||||
@@ -99,7 +102,7 @@ body {
|
||||
text-align: center;
|
||||
}
|
||||
.standby .big {
|
||||
font-size: 172px;
|
||||
font-size: 130px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.20em;
|
||||
line-height: 1;
|
||||
@@ -129,6 +132,68 @@ body {
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
.foot .warn { color: var(--warn); }
|
||||
#rig { transition: opacity 0.4s ease; }
|
||||
|
||||
/* ───────── Doomguard watermark ─────────
|
||||
Sits at z-index: -1 — below all flow content (HUD, standby, etc.)
|
||||
and below the static canvas, so the white noise paints on top of it.
|
||||
Mostly buried under noise (~4% opacity, blurred); periodic "tune-in"
|
||||
spikes raise opacity + sharpen blur briefly, like a CRT catching signal. */
|
||||
.watermark {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 38vw;
|
||||
height: auto;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
filter: blur(2px) brightness(0.9);
|
||||
animation: logo-tune-in 27s linear infinite;
|
||||
}
|
||||
|
||||
/* Logo is invisible by default. Three irregular "almost-catches-signal"
|
||||
events per loop, each preceded by stutter (rapid opacity flips) and
|
||||
tiny vertical jitter — like a CRT trying to sync. Max opacity ~13%,
|
||||
always blurred — never crisp, always reads as buried in the noise. */
|
||||
@keyframes logo-tune-in {
|
||||
/* — Long quiet — */
|
||||
0%, 22% { opacity: 0; filter: blur(2px) brightness(0.9); transform: translate(-50%, -50%); }
|
||||
|
||||
/* Event 1: stutter in, brief soft hold (~9% peak), stutter out */
|
||||
22.3% { opacity: 0.06; filter: blur(1.4px) brightness(1.0); transform: translate(-50%, -50.3%); }
|
||||
22.6% { opacity: 0; filter: blur(2px) brightness(0.9); transform: translate(-50%, -49.8%); }
|
||||
23% { opacity: 0.09; filter: blur(0.8px) brightness(1.1); transform: translate(-50%, -50%); }
|
||||
24.6% { opacity: 0.09; filter: blur(0.8px) brightness(1.1); transform: translate(-50%, -50%); }
|
||||
25% { opacity: 0; filter: blur(2px) brightness(0.9); }
|
||||
25.3% { opacity: 0.04; }
|
||||
25.6% { opacity: 0; }
|
||||
|
||||
/* — Long quiet — */
|
||||
44% { opacity: 0; transform: translate(-50%, -50%); }
|
||||
|
||||
/* Event 2: split-second flutter, signal almost catches but loses it (~5% peak) */
|
||||
44.2% { opacity: 0.05; filter: blur(1.4px) brightness(1.0); transform: translate(-50.3%, -50%); }
|
||||
44.5% { opacity: 0; transform: translate(-50%, -50%); }
|
||||
|
||||
/* — Quiet — */
|
||||
68% { opacity: 0; transform: translate(-50%, -50%); filter: blur(2px) brightness(0.9); }
|
||||
|
||||
/* Event 3: stuttered emergence to longer hold (~13% peak), stuttered exit */
|
||||
68.3% { opacity: 0.05; filter: blur(2px) brightness(0.95); transform: translate(-49.7%, -50%); }
|
||||
68.6% { opacity: 0; }
|
||||
69% { opacity: 0.08; filter: blur(1px) brightness(1.05); transform: translate(-50%, -50.2%); }
|
||||
70.5% { opacity: 0.13; filter: blur(0.4px) brightness(1.15); transform: translate(-50%, -50%); }
|
||||
72% { opacity: 0.10; filter: blur(0.6px) brightness(1.1); }
|
||||
73% { opacity: 0; filter: blur(2px) brightness(0.9); }
|
||||
73.3% { opacity: 0.05; }
|
||||
73.6% { opacity: 0; }
|
||||
|
||||
/* — Quiet to end — */
|
||||
100% { opacity: 0; transform: translate(-50%, -50%); }
|
||||
}
|
||||
|
||||
/* ───────── Overlays ───────── */
|
||||
#static {
|
||||
@@ -183,6 +248,11 @@ body {
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<img class="watermark"
|
||||
src="file:///home/jin/cloud.jakubzych.com/_img/logos/dgw.png"
|
||||
alt=""
|
||||
aria-hidden="true">
|
||||
|
||||
<header class="hud">
|
||||
<div class="group">
|
||||
<span><span class="led"></span>TRANSMISSION</span>
|
||||
@@ -222,7 +292,7 @@ body {
|
||||
|
||||
<footer class="foot">
|
||||
<span>CH 118.0 MHz</span>
|
||||
<span>NTSC · 525i · MONO</span>
|
||||
<span id="rig">— TELEMETRY —</span>
|
||||
<span class="warn">▲ AUDIO ACTIVE</span>
|
||||
</footer>
|
||||
|
||||
@@ -231,6 +301,11 @@ body {
|
||||
<div class="vignette"></div>
|
||||
<div class="flicker"></div>
|
||||
|
||||
<!-- Static device specs as a global. Generated by telemetry.sh.
|
||||
Loaded via <script> instead of fetch() because OBS's CEF blocks
|
||||
file:// → file:// fetch (Chromium "unique origin" rule). -->
|
||||
<script src="telemetry.js"></script>
|
||||
|
||||
<script>
|
||||
// Live UTC clock
|
||||
const clockEl = document.getElementById('clock');
|
||||
@@ -269,6 +344,88 @@ body {
|
||||
};
|
||||
drawNoise();
|
||||
setInterval(drawNoise, 1000 / 12);
|
||||
|
||||
// ───────── Telemetry: rotating real-data subtitle ─────────
|
||||
// All lines derive from telemetry.js (window.__TEL), generated by telemetry.sh.
|
||||
const rigEl = document.getElementById('rig');
|
||||
const tel = window.__TEL || null;
|
||||
|
||||
// Compact CPU model: drop "(R)/(TM)" and "X-Core Processor" filler
|
||||
const shortCpu = (m) => (m || '')
|
||||
.replace(/\(R\)|\(TM\)/g, '')
|
||||
.replace(/\s+\S+-Core\s+Processor\b/i, '')
|
||||
.replace(/\s+Processor\b/i, '')
|
||||
.replace(/\s+CPU\b.*$/i, '')
|
||||
.replace(/\s+@.*$/, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
const lines = [
|
||||
// CPU + MEM — one combined line
|
||||
() => {
|
||||
if (!tel?.cpu?.model) return null;
|
||||
let s = `CPU · ${shortCpu(tel.cpu.model)}`;
|
||||
if (tel.cpu.threads) s += ` · ${tel.cpu.threads}T`;
|
||||
if (tel.mem?.totalGB) s += ` · MEM ${tel.mem.totalGB} GB`;
|
||||
return s;
|
||||
},
|
||||
|
||||
// GPU
|
||||
() => {
|
||||
if (!tel?.gpu?.name) return null;
|
||||
let s = `GPU · ${tel.gpu.name}`;
|
||||
if (tel.gpu.vramTotalMB) s += ` · ${(tel.gpu.vramTotalMB / 1024).toFixed(0)} GB VRAM`;
|
||||
return s;
|
||||
},
|
||||
|
||||
// OBS output: resolution, fps, encoder
|
||||
() => {
|
||||
const o = tel?.obs;
|
||||
if (!o?.output?.w) return null;
|
||||
let s = `OUTPUT · ${o.output.w}×${o.output.h}`;
|
||||
if (o.fps) s += ` @ ${o.fps} FPS`;
|
||||
if (o.stream?.encoder) s += ` · ${o.stream.encoder}`;
|
||||
return s;
|
||||
},
|
||||
|
||||
// OBS stream: rate control + bitrate + h.264 profile
|
||||
() => {
|
||||
const s = tel?.obs?.stream;
|
||||
if (!s) return null;
|
||||
const parts = [];
|
||||
if (s.rateControl) parts.push(s.rateControl);
|
||||
if (s.bitrateKbps) parts.push(`${s.bitrateKbps} kbps`);
|
||||
if (s.profile) parts.push(s.profile.toUpperCase());
|
||||
return parts.length ? `STREAM · ${parts.join(' · ')}` : null;
|
||||
},
|
||||
|
||||
// Kernel
|
||||
() => tel?.host?.kernel ? `KERNEL · ${tel.host.kernel}` : null,
|
||||
];
|
||||
|
||||
let lineIdx = 0;
|
||||
const renderLine = () => {
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const v = lines[lineIdx]();
|
||||
if (v) { rigEl.textContent = v; return; }
|
||||
lineIdx = (lineIdx + 1) % lines.length;
|
||||
}
|
||||
rigEl.textContent = '— TELEMETRY OFFLINE —';
|
||||
};
|
||||
|
||||
// Soft crossfade: 0.4s out → swap text → 0.4s in (matches the CSS transition)
|
||||
const FADE_MS = 400;
|
||||
const swapLine = () => {
|
||||
rigEl.style.opacity = '0';
|
||||
setTimeout(() => { renderLine(); rigEl.style.opacity = '1'; }, FADE_MS);
|
||||
};
|
||||
|
||||
renderLine();
|
||||
rigEl.style.opacity = '1';
|
||||
setInterval(() => {
|
||||
lineIdx = (lineIdx + 1) % lines.length;
|
||||
swapLine();
|
||||
}, 6000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user