Files
obs-config/landing/index.html
Jakub Zych 7a5b1df7b6 OBS state snapshot: scene additions, dock geometry, landing tweaks
Captures the current OBS scene/window state after wiring Music Daemon,
Project Loading, and Game scenes through the UI. Untitled.json now
includes the new scenes and their browser sources.

Landing index.html: reverted to the simpler pre-doomguard version
(static signal bars, plain footer rig, no telemetry rotation, no
watermark animation), keeping the SIGNAL grid centering and the small
#rig opacity transition.

OBS rewrites these files on shutdown — expect future commits to show
similar dock-geometry diff noise.
2026-04-26 02:40:24 +02:00

365 lines
10 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>OPHI-118 / STAND BY</title>
<style>
:root {
--bg: #07080d;
--ink: #e8e8e0;
--hud: #4fd2ff;
--hud-dim: #2a7fa6;
--accent: #e63a2e;
--warn: #ffd000;
--mono: 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', monospace;
--display: 'DejaVu Sans', 'Liberation Sans', 'Helvetica', 'Arial', sans-serif;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
background: var(--bg);
color: var(--ink);
font-family: var(--display);
overflow: hidden;
user-select: none;
}
body {
position: relative;
display: grid;
grid-template-rows: auto 1fr auto auto;
padding: 40px 72px;
gap: 24px;
}
/* ───────── HUD top strip ───────── */
.hud {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
font-family: var(--mono);
font-size: 22px;
font-weight: 700;
color: var(--hud);
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;
width: 11px; height: 11px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent);
margin-right: 12px;
vertical-align: middle;
animation: rec-blink 1.6s ease-in-out infinite;
}
@keyframes rec-blink {
0%, 55% { opacity: 1; }
65%, 100% { opacity: 0.2; }
}
/* ───────── Centered mark ───────── */
.stage {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 36px;
}
.mark {
width: 360px; height: 360px;
/* Static glow only — animated drop-shadow is CPU-expensive in CEF.
The static breathing feel comes from the rec-blink LED + cd-pulse
elsewhere; the mark just has a steady halo. */
filter: drop-shadow(0 0 22px rgba(58, 209, 255, 0.35));
}
.bars {
display: flex;
width: 720px;
height: 14px;
opacity: 0.32;
}
.bars i { flex: 1; display: block; }
.bars i:nth-child(1) { background: #c7c7c7; }
.bars i:nth-child(2) { background: #c7c700; }
.bars i:nth-child(3) { background: #00c7c7; }
.bars i:nth-child(4) { background: #00c700; }
.bars i:nth-child(5) { background: #c700c7; }
.bars i:nth-child(6) { background: #c70000; }
.bars i:nth-child(7) { background: #0000c7; }
/* ───────── Standby block ───────── */
.standby {
text-align: center;
}
.standby .big {
font-size: 130px;
font-weight: 900;
letter-spacing: 0.20em;
line-height: 1;
text-shadow: 0 0 18px rgba(79, 210, 255, 0.18);
animation: standby-pulse 3s ease-in-out infinite;
}
.standby .sub {
margin-top: 30px;
font-family: var(--mono);
font-size: 26px;
letter-spacing: 0.45em;
color: var(--hud-dim);
}
@keyframes standby-pulse {
0%, 100% { opacity: 0.90; }
50% { opacity: 1.00; }
}
/* ───────── Bottom HUD ───────── */
.foot {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--mono);
font-size: 19px;
color: var(--hud-dim);
letter-spacing: 0.12em;
}
.foot .warn { color: var(--warn); }
#rig { transition: opacity 0.4s ease; }
/* ───────── Overlays ───────── */
#static {
position: absolute; inset: 0;
width: 100%; height: 100%;
pointer-events: none;
opacity: 0.045;
mix-blend-mode: screen;
image-rendering: pixelated;
}
.scanlines {
position: absolute; inset: 0;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
rgba(0,0,0,0) 0px,
rgba(0,0,0,0) 2px,
rgba(0,0,0,0.20) 3px,
rgba(0,0,0,0.20) 4px
);
opacity: 0.5;
mix-blend-mode: multiply;
}
.vignette {
position: absolute; inset: 0;
pointer-events: none;
background: radial-gradient(
ellipse at center,
rgba(0,0,0,0) 45%,
rgba(0,0,0,0.60) 100%
);
}
.flicker {
position: absolute; inset: 0;
pointer-events: none;
background: rgba(0,0,0,0);
animation: flicker 11s ease-in-out infinite;
}
@keyframes flicker {
0%, 100% { background: rgba(0,0,0,0); }
18% { background: rgba(0,0,0,0); }
18.3% { background: rgba(0,0,0,0.06); }
18.5% { background: rgba(0,0,0,0); }
47% { background: rgba(0,0,0,0); }
47.3% { background: rgba(0,0,0,0.08); }
47.5% { background: rgba(0,0,0,0); }
78% { background: rgba(0,0,0,0); }
78.4% { background: rgba(0,0,0,0.05); }
78.7% { background: rgba(0,0,0,0); }
}
</style>
</head>
<body>
<header class="hud">
<div class="group">
<span><span class="led"></span>TRANSMISSION</span>
<span>OPHI-118</span>
</div>
<div class="group">
<span class="dim">SIGNAL</span>
<span id="signal">&#x25AE;&#x25AE;&#x25AE;&#x25AF;&#x25AF;</span>
</div>
<div class="group">
<span class="dim">UTC</span>
<span id="clock">--:--:--</span>
</div>
</header>
<main class="stage">
<svg class="mark" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<circle cx="100" cy="100" r="92" fill="none" stroke="#3ad1ff" stroke-width="1.2" opacity="0.35"/>
<circle cx="100" cy="100" r="80" fill="none" stroke="#3ad1ff" stroke-width="2.5"/>
<g stroke="#3ad1ff" stroke-width="2" opacity="0.55">
<line x1="100" y1="8" x2="100" y2="22"/>
<line x1="100" y1="178" x2="100" y2="192"/>
<line x1="8" y1="100" x2="22" y2="100"/>
<line x1="178" y1="100" x2="192" y2="100"/>
</g>
<polygon points="100,52 148,140 52,140"
fill="none" stroke="#e63a2e" stroke-width="3.5" stroke-linejoin="miter"/>
<circle cx="100" cy="116" r="4" fill="#ffd000"/>
</svg>
<div class="bars"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
</main>
<section class="standby">
<div class="big">PLEASE STAND BY</div>
<div class="sub">&mdash; DO NOT ADJUST YOUR RECEIVER &mdash;</div>
</section>
<footer class="foot">
<span>CH 118.0 MHz</span>
<span id="rig">&mdash; TELEMETRY &mdash;</span>
<span class="warn">&#x25B2; AUDIO ACTIVE</span>
</footer>
<canvas id="static"></canvas>
<div class="scanlines"></div>
<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');
const pad = n => String(n).padStart(2, '0');
const tickClock = () => {
const d = new Date();
clockEl.textContent = `${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}`;
};
tickClock();
setInterval(tickClock, 1000);
// Signal bar fluctuation — 3 bars typical, occasional drop or spike
const sigEl = document.getElementById('signal');
const FILL = '▮', EMPTY = '▯';
const drawSig = n => FILL.repeat(n) + EMPTY.repeat(5 - n);
setInterval(() => {
const r = Math.random();
const n = r < 0.06 ? 2 : r < 0.70 ? 3 : r < 0.95 ? 4 : 5;
sigEl.textContent = drawSig(n);
}, 1500);
// Animated CRT static (small buffer, scaled by browser)
const cv = document.getElementById('static');
const ctx = cv.getContext('2d');
const W = 320, H = 180;
cv.width = W; cv.height = H;
const img = ctx.createImageData(W, H);
const drawNoise = () => {
const d = img.data;
for (let i = 0; i < d.length; i += 4) {
const v = (Math.random() * 255) | 0;
d[i] = d[i+1] = d[i+2] = v;
d[i+3] = 255;
}
ctx.putImageData(img, 0, 0);
};
drawNoise();
// 8 fps — visually almost identical to 12 fps but ~33% less paint work.
setInterval(drawNoise, 1000 / 8);
// ───────── 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>