Files
obs-config/webapp/public/css/hud.css
2026-05-21 15:00:12 +02:00

690 lines
20 KiB
CSS
Raw Permalink 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.
/* ================================================================
* OPHI-118 shared HUD stylesheet
*
* Extracted from the inline <style> blocks of the seven scene
* overlays. Each scene's Blade view adds only its layout-specific
* rules on top.
* ================================================================ */
:root {
--bg: #07080d;
--ink: #e8e8e0;
--ink-dim: rgba(232, 232, 224, 0.62);
--hud: #4fd2ff;
--hud-dim: #2a7fa6;
--hud-edge: rgba(79, 210, 255, 0.85);
--hud-glow: rgba(79, 210, 255, 0.40);
--accent: #e63a2e;
--warn: #ffd000;
--onair: #5fdc62;
--offair: #ff6f5a;
--term-bg: #04120a;
--term-fg: #5fdc62;
--term-fg-bright: #97f99a;
--term-fg-dim: #2c8d2f;
--term-glow: rgba(80, 220, 100, 0.55);
--term-edge: rgba(80, 220, 100, 0.45);
--term-edge-soft: rgba(80, 220, 100, 0.22);
--frame: rgba(79, 210, 255, 0.85);
--frame-glow: rgba(79, 210, 255, 0.30);
--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%;
color: var(--ink);
font-family: var(--display);
overflow: hidden;
user-select: none;
}
/* Opaque overlay (landing/loading/goodbye/music-box) — full-page background.
Pages add their own grid-template-rows via inline page CSS. */
body.overlay-body {
background: var(--bg);
position: relative;
padding: 40px 72px;
}
/* Transparent overlay (game/desktop) — sits on top of capture sources. */
body.camera-hud-body {
background: transparent;
}
/* ───────── 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 .group > span { display: inline-flex; align-items: center; gap: 12px; }
.hud .led {
display: inline-block;
width: 11px; height: 11px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent);
/* Small upward nudge: flex centers on line-box mid, but caps-only text
reads centered around cap-height mid which sits a touch higher. */
margin-top: -0.08em;
animation: rec-blink 1.6s ease-in-out infinite;
}
.hud--onair .led { background: var(--onair); box-shadow: 0 0 10px var(--onair); }
.hud--offair .led {
background: var(--offair);
box-shadow: 0 0 10px var(--offair);
animation: rec-blink 2.4s ease-in-out infinite;
}
/* The ▮/▯ glyphs in DejaVu Sans Mono are designed centered on x-height,
not cap-height, so against all-caps SIGNAL their visual middle sits low.
Lift by ~(cap-mid x-mid) ≈ 0.10em to put the glyph center on cap center. */
#signal {
display: inline-block;
transform: translateY(-0.10em);
}
@keyframes rec-blink {
0%, 55% { opacity: 1; }
65%, 100% { opacity: 0.2; }
}
/* ───────── Landing-specific station identifier (top HUD) ───────── */
.station {
display: inline-flex;
align-items: baseline;
gap: 0;
white-space: pre;
}
.station .sep { color: var(--hud-dim); margin: 0 0.45em; letter-spacing: 0.32em; }
.station .rig {
display: inline-block;
color: var(--hud);
text-shadow: 0 0 10px rgba(79, 210, 255, 0.55);
font-variant-numeric: tabular-nums;
}
.station .rig::before,
.station .rig::after {
color: var(--hud-dim);
font-weight: 700;
text-shadow: none;
}
.station .rig::before { content: '['; margin-right: 0.32em; }
.station .rig::after { content: ']'; margin-left: 0.32em; }
.station .rig.glitching {
color: var(--warn);
text-shadow: 0 0 12px rgba(255, 208, 0, 0.5);
}
/* ───────── CRT overlays ─────────
Always last in the DOM so they sit above the page content. */
#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); }
}
/* ───────── Terminal block ─────────
Used by loading, goodbye, music-box (full + nc variant). */
.terminal {
position: relative;
overflow: hidden;
background: var(--term-bg);
border: 2px solid var(--term-edge);
border-radius: 6px;
padding: 28px 36px;
font-family: var(--mono);
font-size: 26px;
line-height: 1.55;
color: var(--term-fg);
text-shadow: 0 0 6px var(--term-glow);
box-shadow:
0 0 60px rgba(80, 220, 100, 0.10),
inset 0 0 90px rgba(0, 30, 0, 0.65);
display: flex;
flex-direction: column;
}
.terminal::after {
content: '';
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.28) 3px,
rgba(0,0,0,0.28) 4px
);
opacity: 0.55;
mix-blend-mode: multiply;
}
/* Terminal output buffer — bottom-anchored, scrolls off the top. */
#termOutput {
flex: 1 1 auto;
min-height: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
overflow: hidden;
}
.term-line { white-space: pre-wrap; word-break: break-word; }
.term-prompt { color: var(--term-fg-bright); }
.term-out { color: var(--term-fg); }
.term-dim { color: var(--term-fg-dim); }
.term-warn { color: var(--warn); }
.term-cursor {
display: inline-block;
width: 0.55em;
height: 1em;
background: var(--term-fg);
margin-left: 6px;
vertical-align: -0.15em;
box-shadow: 0 0 8px var(--term-glow);
animation: term-blink 1.05s steps(2) infinite;
}
@keyframes term-blink { 50% { opacity: 0; } }
.pin-spacer { height: 14px; }
.np-line,
.term-now-playing { margin-top: 10px; }
.np-line .np-arrow,
.term-now-playing .np-arrow {
color: var(--term-fg-bright);
margin-right: 12px;
display: inline-block;
animation: np-pulse 1.05s ease-in-out infinite alternate;
}
.np-line .np-title,
.term-now-playing .np-title { color: var(--term-fg-bright); }
.np-line .np-time,
.term-now-playing .np-time { color: var(--term-fg-dim); margin-left: 14px; }
@keyframes np-pulse {
0% { opacity: 0.45; }
100% { opacity: 1.00; }
}
.next-line .next-arrow { color: var(--term-fg-dim); margin-right: 10px; }
.next-line .next-label { color: var(--term-fg-dim); margin-right: 6px; }
.next-line .next-title { color: var(--term-fg); }
.next-line .next-title.empty { color: var(--term-fg-dim); }
/* ───────── Standby / banner ───────── */
.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; }
}
.banner { text-align: center; }
.banner .label {
font-family: var(--mono);
font-size: 22px;
letter-spacing: 0.45em;
text-indent: 0.45em;
color: var(--hud-dim);
margin-bottom: 12px;
}
.banner .title {
font-family: var(--mono);
font-size: 116px;
font-weight: 900;
letter-spacing: 0.18em;
text-indent: 0.18em;
line-height: 1;
color: var(--ink);
text-shadow:
0 0 22px rgba(255, 111, 90, 0.18),
0 0 48px rgba(230, 58, 46, 0.10);
animation: sign-pulse 3.6s ease-in-out infinite;
}
.banner.banner--green .title {
font-size: 88px;
text-shadow:
0 0 22px rgba(95, 220, 98, 0.18),
0 0 48px rgba(80, 200, 100, 0.10);
}
.banner .sub {
margin-top: 18px;
font-family: var(--mono);
font-size: 22px;
letter-spacing: 0.45em;
text-indent: 0.45em;
color: var(--hud-dim);
}
@keyframes sign-pulse {
0%, 100% { opacity: 0.88; }
50% { opacity: 1.00; }
}
/* Countdown (loading) */
.countdown { text-align: center; }
.countdown .label {
font-family: var(--mono);
font-size: 22px;
letter-spacing: 0.45em;
text-indent: 0.45em;
color: var(--hud-dim);
margin-bottom: 12px;
}
.countdown .digits {
font-family: var(--mono);
font-size: 132px;
font-weight: 900;
letter-spacing: 0.10em;
text-indent: 0.10em;
line-height: 1;
color: var(--ink);
text-shadow: 0 0 22px rgba(79, 210, 255, 0.18);
animation: cd-pulse 3s ease-in-out infinite;
}
.countdown .sub {
margin-top: 18px;
font-family: var(--mono);
font-size: 22px;
letter-spacing: 0.45em;
text-indent: 0.45em;
color: var(--hud-dim);
}
@keyframes cd-pulse {
0%, 100% { opacity: 0.92; }
50% { opacity: 1.00; }
}
.countdown.ready .digits {
color: var(--term-fg);
text-shadow: 0 0 32px var(--term-glow);
letter-spacing: 0.18em;
text-indent: 0.18em;
}
.countdown.ready .sub { color: var(--term-fg-bright); }
.countdown.ready .label { color: var(--term-fg-dim); }
/* ───────── Foot (3-col) ───────── */
.foot {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
font-family: var(--mono);
font-size: 19px;
color: var(--hud-dim);
letter-spacing: 0.12em;
}
.foot > *:nth-child(1) { justify-self: start; }
.foot > *:nth-child(2) { justify-self: center; }
.foot > *:nth-child(3) { justify-self: end; }
.foot .warn { color: var(--warn); }
.foot .onair { color: var(--onair); }
.foot .offair { color: var(--offair); }
#rig.glitching {
color: var(--warn);
text-shadow: 0 0 10px rgba(255, 208, 0, 0.4);
}
/* ───────── Status strip (chat help — music-box / nc) ───────── */
.status-strip {
display: flex;
align-items: center;
justify-content: center;
gap: 28px;
font-family: var(--mono);
font-size: 18px;
letter-spacing: 0.18em;
color: var(--hud-dim);
padding: 10px 24px;
border-top: 1px solid rgba(79, 210, 255, 0.22);
border-bottom: 1px solid rgba(79, 210, 255, 0.22);
background: rgba(7, 8, 13, 0.55);
}
.status-strip .label { color: var(--hud); letter-spacing: 0.32em; }
.status-strip .cmd-group { display: inline-flex; gap: 18px; }
.status-strip .cmd {
color: var(--term-fg-bright);
text-shadow: 0 0 6px var(--term-glow);
}
.status-strip .desc {
color: var(--hud-dim);
margin-left: 6px;
font-size: 16px;
letter-spacing: 0.10em;
}
/* ───────── Camera + screen frame (game / desktop) ───────── */
.camera-frame,
.screen-frame {
position: absolute;
pointer-events: none;
}
.camera-frame {
/* The four --cam-* vars are auto-synced from the OBS scene item transform.
Defaults set in the page <style> for first paint. */
top: calc(var(--cam-y) - var(--cam-pad));
left: calc(var(--cam-x) - var(--cam-pad));
width: calc(var(--cam-w) + var(--cam-pad) * 2);
height: calc(var(--cam-h) + var(--cam-pad) * 2);
background: rgba(6, 10, 18, 0.85);
border: 1.5px solid rgba(79, 210, 255, 0.55);
box-shadow:
0 0 28px rgba(79, 210, 255, 0.20),
inset 0 0 60px rgba(0, 0, 0, 0.55);
}
.screen-frame {
top: calc(var(--scr-y) - var(--scr-pad));
left: calc(var(--scr-x) - var(--scr-pad));
width: calc(var(--scr-w) + var(--scr-pad) * 2);
height: calc(var(--scr-h) + var(--scr-pad) * 2);
background: transparent;
border: 1px solid rgba(79, 210, 255, 0.30);
box-shadow: 0 0 18px rgba(79, 210, 255, 0.08);
}
.camera-frame .tick,
.screen-frame .tick {
position: absolute;
width: 24px; height: 24px;
border: 3px solid var(--hud-edge);
box-shadow: 0 0 8px var(--hud-glow);
}
.camera-frame .tick.tl, .screen-frame .tick.tl { top: -2px; left: -2px; border-right: none; border-bottom: none; }
.camera-frame .tick.tr, .screen-frame .tick.tr { top: -2px; right: -2px; border-left: none; border-bottom: none; }
.camera-frame .tick.bl, .screen-frame .tick.bl { bottom: -2px; left: -2px; border-right: none; border-top: none; }
.camera-frame .tick.br, .screen-frame .tick.br { bottom: -2px; right: -2px; border-left: none; border-top: none; }
.camera-frame .label,
.screen-frame .label {
position: absolute;
top: 10px; left: 14px;
font-family: var(--mono);
font-size: 12px;
color: var(--hud);
letter-spacing: 0.32em;
text-shadow: 0 0 4px rgba(7, 8, 13, 0.9);
}
.camera-frame .placeholder {
position: absolute; inset: 0;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 14px;
font-family: var(--mono);
color: var(--hud);
pointer-events: none;
}
.camera-frame .placeholder svg { opacity: 0.6; }
.camera-frame .placeholder .ph-text {
font-size: 13px;
letter-spacing: 0.45em;
color: rgba(79, 210, 255, 0.7);
}
.camera-frame .placeholder .ph-coords {
font-size: 11px;
letter-spacing: 0.20em;
color: var(--hud-dim);
}
.camera-frame.off,
.screen-frame.off { display: none; }
/* ───────── Camera-HUD bottom status bar (game / desktop) ───────── */
.status-bar {
position: absolute;
bottom: 0;
left: 0; right: 0;
padding: 22px 72px 26px;
background:
linear-gradient(
to top,
rgba(7, 8, 13, 0.96) 0%,
rgba(7, 8, 13, 0.96) 65%,
rgba(7, 8, 13, 0.50) 90%,
rgba(7, 8, 13, 0) 100%
);
border-top: 1px solid rgba(79, 210, 255, 0.28);
box-shadow: 0 -2px 16px rgba(0, 0, 0, 0.5);
font-family: var(--mono);
letter-spacing: 0.08em;
}
.status-line { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }
.status-line + .status-line { margin-top: 8px; }
.status-line .sep { color: var(--hud-dim); }
.status-line .spacer { flex: 1 1 auto; }
.live-block {
display: flex; align-items: center; gap: 10px;
color: var(--accent);
font-weight: 700;
letter-spacing: 0.14em;
font-size: 18px;
}
.live-dot {
width: 11px; height: 11px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent);
animation: rec-blink 1.6s ease-in-out infinite;
}
.scene-name,
.game-name {
color: var(--ink);
font-weight: 700;
font-size: 22px;
letter-spacing: 0.10em;
}
.game-mode {
color: var(--hud);
font-size: 17px;
letter-spacing: 0.18em;
}
.hud-tag {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--hud);
font-weight: 700;
font-size: 16px;
letter-spacing: 0.08em;
text-shadow: 0 0 6px rgba(7, 8, 13, 0.9);
}
.hud-tag .dim { color: var(--hud-dim); }
.meta-line {
color: var(--ink-dim);
font-size: 16px;
}
.meta-line .icon { color: var(--hud); }
.np-block { display: flex; align-items: center; gap: 10px; visibility: hidden; }
.np-block.on { visibility: visible; }
.np-block .arrow {
color: var(--term-fg);
animation: np-pulse 1.05s ease-in-out infinite alternate;
text-shadow: 0 0 6px rgba(80, 220, 100, 0.4);
}
.np-block .title { color: var(--ink); }
.np-block .time { color: var(--ink-dim); }
/* Olden Era matchup — right-aligned on the status-bar's LIVE/game-name line. */
.matchup {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 14px;
font-size: 16px;
letter-spacing: 0.16em;
}
.matchup .m-side {
display: inline-flex;
align-items: center;
gap: 10px;
}
.matchup .m-faction {
width: 34px;
height: 34px;
object-fit: contain;
filter: drop-shadow(0 0 6px rgba(7, 8, 13, 0.9));
}
.matchup .m-hero {
width: 38px;
height: 38px;
object-fit: cover;
border-radius: 50%;
border: 1px solid rgba(232, 232, 224, 0.35);
background: rgba(0, 0, 0, 0.4);
}
.matchup .m-name {
color: var(--ink);
font-weight: 700;
letter-spacing: 0.14em;
}
.matchup .m-vs {
color: var(--hud-dim);
font-weight: 700;
letter-spacing: 0.32em;
font-size: 14px;
}
.matchup .m-on {
color: var(--hud-dim);
font-size: 13px;
letter-spacing: 0.32em;
margin-left: 6px;
}
.matchup .m-map {
color: var(--hud);
font-size: 15px;
letter-spacing: 0.18em;
}
.matchup-red .m-my .m-name {
color: #ff6a5e;
text-shadow: 0 0 6px rgba(230, 58, 46, 0.55);
}
.matchup-blue .m-my .m-name {
color: #6ec5ff;
text-shadow: 0 0 6px rgba(58, 166, 255, 0.55);
}
.matchup-red .m-my .m-hero { border-color: rgba(230, 58, 46, 0.7); }
.matchup-blue .m-my .m-hero { border-color: rgba(58, 166, 255, 0.7); }
/* ────────────────────────────────────────────────────────────────
* Webapp navigation breadcrumb (used by /, /scenes, /onboard).
* Distinct from the in-scene .hud strip so overlays stay untouched.
* ──────────────────────────────────────────────────────────────── */
.crumb-bar {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
font-family: var(--mono);
font-size: 14px;
font-weight: 700;
color: var(--hud);
letter-spacing: 0.10em;
padding-bottom: 14px;
margin-bottom: 28px;
border-bottom: 1px solid rgba(79, 210, 255, 0.25);
}
.crumb-bar > :nth-child(2) { justify-self: center; }
.crumb-bar > :nth-child(3) { justify-self: end; }
.crumbs { display: flex; align-items: center; gap: 12px; }
.crumbs .led {
display: inline-block;
width: 9px; height: 9px;
border-radius: 50%;
background: var(--onair);
box-shadow: 0 0 8px var(--onair);
animation: rec-blink 1.6s ease-in-out infinite;
}
.crumb { color: var(--hud); text-decoration: none; transition: color 120ms ease, text-shadow 120ms ease; }
.crumb-root { color: var(--hud-dim); }
.crumb-root:hover{ color: var(--hud); text-shadow: 0 0 8px var(--hud-glow); }
.crumb-here { color: var(--hud); text-shadow: 0 0 8px var(--hud-glow); cursor: default; }
.crumbs .sep { color: var(--hud-dim); opacity: 0.6; }
.crumb-ctx, .crumb-clock { color: var(--hud); }
.crumb-ctx .dim, .crumb-clock .dim { color: var(--hud-dim); margin-right: 8px; }
/* ────────────────────────────────────────────────────────────────
* Webapp page shell — used by every non-overlay browser page
* (/, /scenes, /onboard) so the crumb-bar and content sit at the
* same viewport anchor on every page.
* Overlays keep .overlay-body / .camera-hud-body for OBS sizing.
* ──────────────────────────────────────────────────────────────── */
body.page-shell {
background: var(--bg);
position: relative;
padding: 32px 56px;
min-height: 100vh;
overflow: auto;
}