Files
pondering-orb/web/index.html
T
Diablo_Rain a1e3e6852f feat: Phase 1.5 — health prober, Oracle's Memory, off-thread EPG, PWA
Resilience + scale pass on top of the v1 core.

Stream health prober:
- New SSRF-validated /probe endpoint (HEAD -> ranged-GET fallback, 6s timeout,
  5-min TTL cache, concurrency-capped, no-store) + orb_probe_total metrics
- Front-end health.js: lazy bounded-concurrency probing (current + sampled
  channels, not the whole catalog), LIVE/DEGRADED/DEAD scoring, guide skull
  glyph, instant no-signal on zap to a known-dead channel

Oracle's Memory (memory.js):
- Session resurrection (last channel, volume, mute, theater/dim/ambient)
- Exponential-decay per-channel uptime history; pinning; dead-channel auto-skip

Off-thread XMLTV:
- lib/xmltv-parse.js (pure, hand-rolled scanner, no DOMParser/regex backtracking)
  run in epg-worker.js module worker; wired into the iptv-org provider
- Unit-tested in node: entity decode, TZ offsets, 20k programmes off-thread

Service worker / PWA:
- manifest.webmanifest + orb icons; sw.js (cache-first shell + hls.js,
  stale-while-revalidate for JSON-over-relay, never caches streams)
- CSP gains worker-src + manifest-src; relay sends no-store on /relay + /probe
  so the SW is the sole metadata freshness authority; webmanifest MIME fix

Docs: README de-emojified (title only) and reframed as a peer parallel build.

Verified: SSRF probes blocked, legit probe + cache, new headers present,
PWA assets served, parser tests 8/8, JS syntax clean, zero Rust warnings.

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

137 lines
6.3 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>The Pondering Orb</title>
<!-- DEV baseline CSP (meta). The Rust server sends a STRICTER same-origin CSP header
in production; when both are present the browser enforces the intersection, so
this loose-for-standalone policy never weakens prod. https: here only lets the
mock realm play CORS-clean test streams when served by a plain static server. -->
<meta http-equiv="Content-Security-Policy" content="
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data: https:;
media-src 'self' blob: https:;
connect-src 'self' https:;
font-src 'self';
worker-src 'self';
manifest-src 'self';
object-src 'none';
base-uri 'none';
frame-src 'none';
form-action 'none'">
<meta name="referrer" content="no-referrer">
<meta name="color-scheme" content="dark">
<meta name="theme-color" content="#e10000">
<link rel="manifest" href="manifest.webmanifest">
<link rel="apple-touch-icon" href="icons/apple-touch-icon.png">
<link rel="icon" href="icons/icon-192.png" type="image/png">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="orb-root" class="orb-root" data-mode-theater="false" data-mode-dim="false" data-mode-ambient="false">
<!-- ============ THE SCREEN (the orb) ============ -->
<div id="screen" class="screen" role="region" aria-label="TV player">
<canvas id="ambient" class="ambient" aria-hidden="true"></canvas>
<div class="bezel">
<video id="v" class="video" playsinline aria-label="Live TV stream"></video>
<!-- scrying static (channel-change transition) -->
<canvas id="static" class="static" aria-hidden="true"></canvas>
<!-- the orb clouds over (dead-stream / no-signal) -->
<div id="nosignal" class="nosignal" hidden role="status" aria-live="polite">
<div class="nosignal-bars" aria-hidden="true"></div>
<div class="nosignal-text" data-lex="noSignal">THE ORB CLOUDS OVER</div>
<div class="nosignal-sub" data-lex="noSignalSub">no signal — seeking another vision</div>
</div>
<!-- channel banner OSD (now-playing) -->
<div id="osd-channel" class="osd osd-channel" hidden role="status" aria-live="polite" aria-atomic="true">
<img id="osd-logo" class="osd-logo" alt="" width="96" height="54">
<div class="osd-meta">
<div class="osd-line1"><span id="osd-num" class="osd-num">--</span><span id="osd-name" class="osd-name"></span></div>
<div id="osd-now" class="osd-now"></div>
<div class="osd-prog"><span id="osd-prog-fill" class="osd-prog-fill"></span></div>
</div>
<div id="osd-live" class="osd-live" hidden><span class="live-dot" aria-hidden="true"></span><span data-lex="live">LIVE</span></div>
</div>
<!-- incantation (number entry) OSD -->
<div id="osd-number" class="osd osd-number" hidden role="status" aria-live="assertive" aria-atomic="true">
<span id="osd-number-val" class="seg">---</span>
</div>
<!-- ghost controls -->
<div id="controls" class="controls" role="toolbar" aria-label="Player controls">
<button id="btn-play" class="ctl" aria-label="Play/Pause" aria-pressed="false"></button>
<button id="btn-chdn" class="ctl" aria-label="Channel down">CH ▼</button>
<button id="btn-chup" class="ctl" aria-label="Channel up">CH ▲</button>
<div class="ctl-live" id="ctl-live-wrap" hidden><span class="live-dot" aria-hidden="true"></span><span data-lex="live">LIVE</span></div>
<span id="ctl-clock" class="ctl-clock" aria-hidden="true">--:--</span>
<span class="ctl-spacer"></span>
<button id="btn-mute" class="ctl" aria-label="Mute" aria-pressed="false">🔊</button>
<input id="vol" class="vol" type="range" min="0" max="100" value="100" aria-label="Volume">
<button id="btn-cc" class="ctl" aria-label="Captions" aria-pressed="false">CC</button>
<button id="btn-qual" class="ctl" aria-label="Quality">AUTO</button>
<button id="btn-pip" class="ctl" aria-label="Picture in picture" hidden>PiP</button>
<button id="btn-guide" class="ctl" aria-label="TV guide" data-lex="guideShort">Almanac</button>
<button id="btn-full" class="ctl" aria-label="Fullscreen"></button>
</div>
<!-- quality menu -->
<div id="qual-menu" class="qual-menu" hidden role="menu" aria-label="Quality"></div>
<!-- CRT overlays (scanlines / vignette) -->
<div class="crt-scan" aria-hidden="true"></div>
<div class="crt-vignette" aria-hidden="true"></div>
</div>
<div class="dim-scrim" id="dim-scrim" aria-hidden="true"></div>
</div>
<!-- ============ THE ALMANAC (guide) ============ -->
<section id="guide" class="guide" hidden aria-label="TV Guide" aria-hidden="true">
<header class="guide-head">
<h2 class="guide-title" data-lex="guide">The Almanac</h2>
<div class="guide-clock" id="guide-clock">--:--</div>
<button id="guide-close" class="ctl" aria-label="Close guide"></button>
</header>
<div class="guide-scroll" id="guide-scroll" role="grid" aria-label="Channel schedule" tabindex="0">
<div class="guide-timehdr" id="guide-timehdr" role="row" aria-hidden="true"></div>
<div class="guide-rows" id="guide-rows"></div>
<div class="guide-playhead" id="guide-playhead" aria-hidden="true"></div>
</div>
</section>
<!-- ============ INCANTATIONS (keyboard help) ============ -->
<div id="help" class="help" hidden role="dialog" aria-modal="true" aria-label="Keyboard shortcuts">
<div class="help-card">
<h2 data-lex="help">Incantations</h2>
<dl id="help-list"></dl>
<button id="help-close" class="ctl" aria-label="Close">close (Esc)</button>
<label class="mundane-toggle">
<input type="checkbox" id="mundane"> plain-English labels (mundane mode)
</label>
</div>
</div>
<!-- brand bug -->
<div class="brand-bug" aria-hidden="true" data-lex="brand">THE PONDERING ORB</div>
</div>
<!-- HLS engine: local, pinned (v1.5.17), SRI-verified. -->
<script src="lib/hls.min.js"
integrity="sha384-9v3HcdYrO3D+OPDTjZ40RXocgE4GtXVCd3/mCS62JsM93JXgI1afJVuwjFvsu6ni"
crossorigin="anonymous"></script>
<script type="module" src="app.js"></script>
</body>
</html>