Files
pondering-orb/web/styles.css
T
Diablo_Rain 0b761f684b harden: relay timeouts, DNS resolver reuse, cache hygiene + frontend polish
Server (relay/SSRF):
- Bound connect+TLS+request under CONNECT_TIMEOUT and body download under
  BODY_TIMEOUT so a stalling upstream can't pin a relay permit indefinitely
- Add inbound header_read_timeout to defeat slow-loris connections
- Allowlist relayed Content-Type (fall back to octet-stream) to block
  content-confusion from attacker-controlled stream origins
- Reuse one DNS resolver process-wide instead of rebuilding it per request
  (restores hickory's DNS cache; drops per-segment resolver setup)
- Probe cache: random per-process hash seed (DefaultHasher was deterministic)
  + bounded growth with expired-entry eviction
- Real Ctrl-C handler for a clean shutdown (honours the banner)
- Collapse the wrap_raw no-op alias into wrap

Frontend:
- Guide: visible focus ring tracking arrow-key navigation
- Player: buffering/tuning indicator while <video> is stalled
- prefers-contrast: more pass (lifts --ink-dim to meet WCAG AA)
- Service worker: date-stamped cache names so shell updates propagate

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 14:20:06 -04:00

243 lines
13 KiB
CSS

/* The Pondering Orb — red/black phosphor CRT theme.
All CRT effects are CSS (no WebGL). prefers-reduced-motion kills every animation. */
:root{
--red: #ff2b2b;
--red-dim: #b21d1d;
--red-deep: #6e0f0f;
--red-glow: rgba(255,43,43,.55);
--amber: #ff6a3d;
--bg: #050102;
--panel: #120406;
--panel-2: #1a0608;
--line: #3a0d0f;
--ink: #ffd9d4; /* warm off-white text on red/black */
--ink-dim: #c98b86;
--seg-on: #ff2b2b;
--seg-off: #2a0809;
--focus: #ff2b2b;
--osd-bg: rgba(10,1,2,.82);
--ease: cubic-bezier(.2,.7,.2,1);
}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--ink);
font-family:"Consolas","DejaVu Sans Mono","Courier New",monospace;overflow:hidden}
body{line-height:1.4}
button{font-family:inherit;color:inherit}
img{display:block}
/* ===================== layout root ===================== */
.orb-root{position:fixed;inset:0;display:flex;flex-direction:column;background:radial-gradient(120% 90% at 50% 0%, #160406 0%, var(--bg) 70%)}
/* ===================== the screen ===================== */
.screen{position:relative;flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;
padding:clamp(8px,2vw,28px);transition:padding .25s var(--ease)}
.bezel{position:relative;width:100%;height:100%;max-width:100%;max-height:100%;aspect-ratio:16/9;
margin:auto;background:#000;border-radius:18px/26px;overflow:hidden;
box-shadow:0 0 0 2px #240608, 0 0 0 10px #0c0203, 0 0 60px rgba(0,0,0,.9),
inset 0 0 120px rgba(0,0,0,.9);}
.video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000}
/* ambient backlight canvas sits BEHIND the bezel */
.ambient{position:absolute;inset:0;width:100%;height:100%;
filter:blur(60px) saturate(1.7);transform:scale(1.18);opacity:0;
transition:opacity .5s var(--ease);pointer-events:none;z-index:0}
.orb-root[data-mode-ambient="true"] .ambient{opacity:.85}
.screen{z-index:1}
/* ---- CRT overlays ---- */
.crt-scan{position:absolute;inset:0;pointer-events:none;z-index:6;
background:repeating-linear-gradient(to bottom, rgba(0,0,0,0) 0, rgba(0,0,0,0) 2px, rgba(0,0,0,.16) 3px)}
.crt-vignette{position:absolute;inset:0;pointer-events:none;z-index:6;
background:radial-gradient(120% 120% at 50% 50%, rgba(0,0,0,0) 55%, rgba(0,0,0,.55) 100%);
animation:breathe 4s ease-in-out infinite}
@keyframes breathe{0%,100%{transform:scale(1);opacity:.85}50%{transform:scale(1.012);opacity:1}}
/* ===================== OSD (shared) ===================== */
.osd{position:absolute;z-index:7;background:var(--osd-bg);border:1px solid var(--line);
color:var(--ink);backdrop-filter:blur(2px);box-shadow:0 0 18px rgba(0,0,0,.7)}
.osd[hidden]{display:none}
/* channel banner */
.osd-channel{left:18px;bottom:18px;display:flex;gap:14px;align-items:center;padding:12px 16px;border-radius:8px;
min-width:300px;max-width:70%;opacity:0;transform:translateY(8px) scale(.98);
transition:opacity .3s var(--ease),transform .3s var(--ease)}
.osd-channel.show{opacity:1;transform:none}
.osd-logo{width:96px;height:54px;object-fit:contain;background:#000;border:1px solid var(--line);border-radius:4px}
.osd-meta{min-width:0;flex:1}
.osd-line1{display:flex;align-items:baseline;gap:10px}
.osd-num{font-weight:bold;color:var(--red);font-size:26px;text-shadow:0 0 10px var(--red-glow)}
.osd-name{font-size:18px;font-weight:bold;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.osd-now{color:var(--ink-dim);font-size:13px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.osd-prog{height:4px;background:var(--seg-off);border-radius:2px;margin-top:7px;overflow:hidden}
.osd-prog-fill{display:block;height:100%;width:0;background:var(--red);box-shadow:0 0 8px var(--red-glow)}
.osd-live{display:flex;align-items:center;gap:6px;color:var(--red);font-weight:bold;font-size:13px;align-self:flex-start}
/* number entry (7-seg look) */
.osd-number{right:22px;top:22px;padding:8px 14px;border-radius:6px}
.seg{font-weight:bold;font-size:40px;letter-spacing:6px;color:var(--seg-on);
text-shadow:0 0 14px var(--red-glow);font-variant-numeric:tabular-nums}
.live-dot{width:9px;height:9px;border-radius:50%;background:var(--red);box-shadow:0 0 8px var(--red);
display:inline-block;animation:pulse 2s ease-in-out infinite}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
/* ===================== scrying static ===================== */
.static{position:absolute;inset:0;width:100%;height:100%;z-index:5;opacity:0;pointer-events:none;
transition:opacity .12s linear}
.static.show{opacity:.9}
/* ===================== buffering / tuning ===================== */
/* Corner hint shown while <video> is stalled — top-left keeps clear of the
brand bug (top-right) and the channel banner (bottom-left). */
.screen.buffering .bezel::after{
content:"\21BB tuning\2026";
position:absolute;left:18px;top:14px;z-index:7;
font-size:12px;letter-spacing:2px;color:var(--red);
text-shadow:0 0 10px var(--red-glow);
background:var(--osd-bg);border:1px solid var(--line);
padding:4px 10px;border-radius:6px;
animation:orb-buf 1.1s ease-in-out infinite}
@keyframes orb-buf{0%,100%{opacity:.45}50%{opacity:1}}
/* ===================== the orb clouds over ===================== */
.nosignal{position:absolute;inset:0;z-index:5;display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:10px;background:#050102;text-align:center;padding:20px}
.nosignal[hidden]{display:none}
.nosignal-bars{width:min(70%,520px);height:46%;border:1px solid var(--line);
background:linear-gradient(90deg,#6e0f0f 0 14%,#b21d1d 14% 28%,#ff6a3d 28% 42%,#ffd9d4 42% 56%,
#b21d1d 56% 70%,#6e0f0f 70% 84%,#1a0608 84% 100%);opacity:.5;filter:saturate(.8)}
.nosignal-text{color:var(--red);font-weight:bold;font-size:clamp(18px,3vw,30px);letter-spacing:4px;
text-shadow:0 0 14px var(--red-glow)}
.nosignal-sub{color:var(--ink-dim);font-size:13px;letter-spacing:1px}
/* ===================== ghost controls ===================== */
.controls{position:absolute;left:0;right:0;bottom:0;z-index:8;display:flex;align-items:center;gap:8px;
padding:12px 14px;background:linear-gradient(to top, rgba(5,1,2,.92), rgba(5,1,2,0));
opacity:0;transform:translateY(6px);transition:opacity .25s var(--ease),transform .25s var(--ease);
pointer-events:none;flex-wrap:wrap}
.screen.controls-visible .controls,.controls:focus-within{opacity:1;transform:none;pointer-events:auto}
.ctl{background:var(--panel);border:1px solid var(--line);color:var(--ink);border-radius:6px;
padding:6px 10px;font-size:13px;cursor:pointer;min-height:34px;transition:border-color .15s,box-shadow .15s,color .15s}
.ctl:hover{border-color:var(--red-dim);color:#fff}
.ctl-spacer{flex:1 1 auto}
.ctl-clock{color:var(--ink-dim);font-size:13px;font-variant-numeric:tabular-nums;padding:0 4px}
.ctl-live{display:flex;align-items:center;gap:6px;color:var(--red);font-weight:bold;font-size:12px}
.vol{width:90px;accent-color:var(--red)}
.qual-menu{position:absolute;right:14px;bottom:58px;z-index:9;background:var(--panel-2);border:1px solid var(--line);
border-radius:6px;padding:4px;min-width:120px;box-shadow:0 0 18px rgba(0,0,0,.8)}
.qual-menu[hidden]{display:none}
.qual-menu button{display:block;width:100%;text-align:left;background:none;border:0;color:var(--ink);
padding:7px 10px;border-radius:4px;cursor:pointer;font-size:13px}
.qual-menu button:hover,.qual-menu button[aria-checked="true"]{background:var(--red-deep);color:#fff}
/* ===================== dim mode ===================== */
.dim-scrim{position:absolute;inset:0;z-index:4;background:rgba(0,0,0,.72);opacity:0;pointer-events:none;
transition:opacity .3s var(--ease)}
.orb-root[data-mode-dim="true"] .dim-scrim{opacity:1}
.orb-root[data-mode-dim="true"] .bezel{z-index:5;position:relative}
.orb-root[data-mode-dim="true"].activity .dim-scrim{opacity:.4}
/* ===================== theater mode ===================== */
.orb-root[data-mode-theater="true"] .screen{padding:0}
.orb-root[data-mode-theater="true"] .bezel{border-radius:0;box-shadow:none}
.orb-root[data-mode-theater="true"] .brand-bug{opacity:0}
/* ===================== the almanac (guide) ===================== */
.guide{position:absolute;left:0;right:0;bottom:0;height:46%;z-index:20;background:var(--panel);
border-top:2px solid var(--red-deep);box-shadow:0 -12px 40px rgba(0,0,0,.8);
transform:translateY(100%);transition:transform .28s var(--ease);display:flex;flex-direction:column}
.guide:not([hidden]){display:flex}
.guide.show{transform:translateY(0)}
.guide-head{display:flex;align-items:center;gap:14px;padding:8px 14px;border-bottom:1px solid var(--line);
background:var(--panel-2)}
.guide-title{margin:0;font-size:15px;letter-spacing:3px;color:var(--red);text-shadow:0 0 10px var(--red-glow)}
.guide-clock{color:var(--ink-dim);font-variant-numeric:tabular-nums;margin-left:auto;font-size:13px}
.guide-scroll{position:relative;flex:1;overflow:auto;outline:none}
.guide-timehdr{position:sticky;top:0;z-index:3;display:flex;height:26px;background:var(--panel-2);
border-bottom:1px solid var(--line);margin-left:var(--ch-col,150px)}
.guide-timehdr .tick{flex:0 0 var(--hour-w,180px);font-size:11px;color:var(--ink-dim);
padding:5px 8px;border-left:1px solid var(--line)}
.guide-rows{position:relative}
.guide-row{display:flex;height:var(--row-h,46px);border-bottom:1px solid #220708}
.guide-chcell{position:sticky;left:0;z-index:2;flex:0 0 var(--ch-col,150px);display:flex;align-items:center;gap:8px;
padding:0 8px;background:var(--panel-2);border-right:1px solid var(--line);cursor:pointer}
.guide-chcell:hover{background:var(--red-deep)}
/* keyboard cursor: arrow-key navigation marks the focused row so it isn't blind */
.guide-row.focused{background:rgba(255,43,43,.07)}
.guide-row.focused .guide-chcell{background:var(--red-deep);
box-shadow:inset 3px 0 0 var(--red),0 0 12px rgba(255,43,43,.25)}
.guide-chcell .num{color:var(--red);font-weight:bold;font-variant-numeric:tabular-nums;min-width:24px}
.guide-chcell .nm{font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.guide-chcell .skull{margin-left:auto;opacity:.55;font-size:12px}
.guide-chcell .pin{background:none;border:0;cursor:pointer;font-size:14px;line-height:1;padding:0 2px;
color:var(--ink-dim);margin-left:6px;flex:0 0 auto}
.guide-chcell .skull + .pin{margin-left:6px}
.guide-chcell .pin:not(.skull + .pin){margin-left:auto}
.guide-chcell .pin.on{color:var(--red);text-shadow:0 0 8px var(--red-glow)}
.guide-chcell .pin:hover{color:#fff}
.guide-progs{position:relative;flex:1}
.guide-prog{position:absolute;top:3px;bottom:3px;background:var(--panel-2);border:1px solid var(--line);
border-radius:4px;padding:5px 8px;font-size:12px;overflow:hidden;cursor:pointer;white-space:nowrap;text-overflow:ellipsis}
.guide-prog:hover,.guide-prog:focus{border-color:var(--red-dim);outline:none}
.guide-prog.airing{background:rgba(255,43,43,.14);border-color:var(--red-dim)}
.guide-prog .t{color:var(--ink-dim);font-size:10px}
.guide-playhead{position:absolute;top:0;bottom:0;width:2px;background:var(--red);box-shadow:0 0 8px var(--red);
z-index:4;pointer-events:none;left:var(--ch-col,150px)}
/* ===================== help / dialog ===================== */
.help{position:absolute;inset:0;z-index:40;display:flex;align-items:center;justify-content:center;
background:rgba(5,1,2,.7);backdrop-filter:blur(2px)}
.help[hidden]{display:none}
.help-card{background:var(--panel-2);border:1px solid var(--red-deep);border-radius:10px;padding:22px 26px;
max-width:560px;width:90%;box-shadow:0 0 50px rgba(0,0,0,.85)}
.help-card h2{margin:0 0 14px;color:var(--red);letter-spacing:3px;text-shadow:0 0 10px var(--red-glow)}
.help-card dl{display:grid;grid-template-columns:auto 1fr;gap:6px 16px;margin:0 0 16px}
.help-card dt{color:var(--red);font-weight:bold;white-space:nowrap}
.help-card dd{margin:0;color:var(--ink-dim)}
.mundane-toggle{display:flex;align-items:center;gap:8px;margin-top:12px;color:var(--ink-dim);font-size:13px}
kbd{background:#000;border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:1px 6px;
font-family:inherit;color:var(--ink)}
/* ===================== brand bug ===================== */
.brand-bug{position:absolute;right:14px;top:10px;z-index:7;font-size:11px;letter-spacing:3px;
color:var(--red);opacity:.6;text-shadow:0 0 8px var(--red-glow);pointer-events:none;transition:opacity .3s}
/* ===================== focus rings (a11y) ===================== */
:focus-visible{outline:2px solid var(--focus);outline-offset:2px;box-shadow:0 0 8px var(--red-glow)}
/* ===================== responsive ===================== */
@media (max-width:768px){
.guide{height:70%}
.osd-channel{min-width:0;max-width:88%}
.vol{display:none}
.brand-bug{display:none}
}
/* ===================== high-contrast pass (a11y) ===================== */
/* Default --ink-dim (#c98b86 on #050102 ≈ 3.5:1) misses WCAG AA for the small
text it's used on everywhere (OSD sub, guide names, time labels, help dd).
Lift it and strengthen panels/borders only when the user asks for it. */
@media (prefers-contrast: more){
:root{
--ink-dim:#ffb8b0;
--line:#5a1416;
--panel:#1e0608;
--panel-2:#260a0c;
}
}
/* ===================== reduced motion kill-switch ===================== */
@media (prefers-reduced-motion: reduce){
.crt-vignette{animation:none}
.live-dot{animation:none}
.ambient{display:none}
.static{display:none}
*{transition-duration:.001ms !important;animation-duration:.001ms !important;animation-iteration-count:1 !important}
}