a1e3e6852f
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>
137 lines
6.3 KiB
HTML
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>
|