By meaning: - Page: app.js keeps the age parameters and paragraph and adds debug=binding; imports, state and roomTest hooks of age, co and binding side by side. index.html carries the co section and the binding table, each with its CSS. - room.e2e.mjs carries scenarioBinding whole beside scenarioAge and scenarioCo; default list `...,age,co` with a room, `binding` always; run.sh's node unit tests are every page module's (capsule, media, presentation, privacy-pass, binding). - wai-live-web: the wai dependency takes co_performance and browser_sink; the crate doc lists both. - joule-moq-web README: one node-test bullet for capsule, media and binding, then privacy-pass and ad-presentation; scenarios age, co and binding. - REGISTRY prose names exploration and browser-sink together; the shared lists keep both sides; export regenerated. Co-Authored-By: Claude Opus 5.5 <[email protected]>
141 lines
8.2 KiB
HTML
141 lines
8.2 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>MoQ room client</title>
|
|
<link rel="icon" href="data:,">
|
|
<style>
|
|
:root { --bg: #f6f7f9; --fg: #15171a; --muted: #5d6470; --panel: #ffffff; --line: #d9dde3; --human: #1f6f43; --machine: #8a4b00; }
|
|
@media (prefers-color-scheme: dark) { :root { --bg: #111317; --fg: #e8eaee; --muted: #9aa2ae; --panel: #1a1d23; --line: #2c313a; --human: #6fd39b; --machine: #f0a957; } }
|
|
* { box-sizing: border-box; }
|
|
[hidden] { display: none !important; }
|
|
body { margin: 0; padding: 16px; background: var(--bg); color: var(--fg); font: 14px/1.45 system-ui, sans-serif; }
|
|
header { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; margin-bottom: 12px; }
|
|
h1 { font-size: 16px; margin: 0; }
|
|
#who { color: var(--muted); font-family: ui-monospace, monospace; }
|
|
main { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 12px; }
|
|
@media (max-width: 760px) { main { grid-template-columns: minmax(0, 1fr); } }
|
|
section { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 10px; min-width: 0; }
|
|
canvas { width: 100%; height: auto; display: block; background: #101218; border-radius: 4px; }
|
|
#caption { min-height: 2.6em; margin-top: 8px; display: flex; gap: 8px; align-items: baseline; flex-wrap: wrap; }
|
|
#caption .origin { font-size: 12px; padding: 1px 6px; border-radius: 3px; border: 1px solid currentColor; }
|
|
#caption .origin-human { color: var(--human); }
|
|
#caption .origin-machine, #caption .origin-machine-reviewed { color: var(--machine); }
|
|
#timeline { list-style: none; margin: 0; padding: 0; max-height: 280px; overflow: auto; font-family: ui-monospace, monospace; font-size: 12px; }
|
|
#timeline li { padding: 2px 0; border-bottom: 1px solid var(--line); overflow-wrap: anywhere; }
|
|
#log { white-space: pre-wrap; font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); max-height: 220px; overflow: auto; margin: 0; overflow-wrap: anywhere; }
|
|
h2 { font-size: 13px; margin: 0 0 6px; color: var(--muted); font-weight: 600; }
|
|
.controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
|
|
#status { color: var(--muted); }
|
|
/* Co-performance: the floor, what's next, the audience and fidelity. */
|
|
.co-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; }
|
|
.co-grid h3 { font-size: 12px; margin: 0 0 4px; color: var(--muted); font-weight: 600; }
|
|
.co-grid ul, .co-grid ol { margin: 0; padding-left: 18px; font-size: 13px; }
|
|
.co-grid li { padding: 1px 0; overflow-wrap: anywhere; }
|
|
#co-now { margin: 0 0 6px; font-weight: 600; }
|
|
#co-marks, #co-left { color: var(--muted); font-size: 12px; margin: 4px 0 0; }
|
|
#co-intents li[data-outcome="upcoming"] { color: var(--machine); }
|
|
#co-intents li[data-outcome="kept"] { color: var(--human); }
|
|
#co-lead { margin-top: 8px; }
|
|
#co-lead input[type="number"] { width: 4.5em; }
|
|
#co-input { margin-top: 6px; }
|
|
#co-options { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px; }
|
|
table.binding { border-collapse: collapse; font-family: ui-monospace, monospace; font-size: 12px; width: 100%; }
|
|
table.binding th, table.binding td { text-align: left; padding: 2px 8px 2px 0; border-bottom: 1px solid var(--line); white-space: nowrap; }
|
|
table.binding tr[data-state="verified"] td:nth-child(2) { color: var(--human); }
|
|
table.binding tr[data-state="refused"] td:nth-child(2), table.binding tr[data-state="unbound"] td:nth-child(2) { color: var(--machine); }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header><h1>MoQ room client</h1><span id="who">loading</span></header>
|
|
<!-- An end-to-end encrypted room: its epoch and the members of its group. -->
|
|
<div id="e2ee" class="controls" hidden><strong>End-to-end encrypted</strong> <span id="e2ee-state"></span></div>
|
|
<!-- With a room service (?service=<url>): enter as a publisher or a viewer. -->
|
|
<div id="room-app" class="controls">
|
|
<button id="join" type="button">Join with camera and microphone</button>
|
|
<button id="watch" type="button">Watch</button>
|
|
<button id="leave" type="button" hidden>Leave</button>
|
|
<span id="status" role="status" aria-live="polite"></span>
|
|
<form id="caption-form" hidden><label for="caption-text">Caption</label> <input id="caption-text" maxlength="200" autocomplete="off"> <button type="submit">Send</button></form>
|
|
</div>
|
|
<main>
|
|
<section>
|
|
<h2>The room, composed</h2>
|
|
<canvas id="canvas" width="640" height="360"></canvas>
|
|
<div id="caption" aria-live="polite"></div>
|
|
<div id="dvr" class="controls" style="margin: 8px 0 0">
|
|
<label for="seek-s">Seconds back</label>
|
|
<input id="seek-s" type="number" min="1" max="900" value="10" style="width: 5em">
|
|
<button id="seek" type="button" disabled>Seek back</button>
|
|
<button id="live" type="button" hidden>Back to live</button>
|
|
<span id="dvr-status" role="status" aria-live="polite"></span>
|
|
</div>
|
|
<canvas id="dvr-canvas" width="640" height="360" hidden style="margin-top: 8px" aria-label="The participant's earlier video, from the relay's window"></canvas>
|
|
</section>
|
|
<section>
|
|
<h2>Events</h2>
|
|
<ul id="timeline"></ul>
|
|
</section>
|
|
<!-- A room with co-performance (WAI co-performance): see co.js. -->
|
|
<section id="co" hidden style="grid-column: 1 / -1" aria-label="Co-performance">
|
|
<h2>Co-performance</h2>
|
|
<div class="co-grid">
|
|
<div>
|
|
<h3>The floor</h3>
|
|
<p id="co-now" role="status" aria-live="polite"></p>
|
|
<ol id="co-turns"></ol>
|
|
<p id="co-marks"></p>
|
|
<div class="controls" style="margin: 6px 0 0"><button id="co-pass" type="button" hidden>Pass the floor</button></div>
|
|
</div>
|
|
<div>
|
|
<h3>What's next</h3>
|
|
<ul id="co-intents" aria-live="polite"></ul>
|
|
</div>
|
|
<div>
|
|
<h3>The audience</h3>
|
|
<p id="co-question"></p>
|
|
<form id="co-input" hidden>
|
|
<div id="co-options"></div>
|
|
<div id="co-prompt-row" hidden><label for="co-prompt">Your prompt</label> <input id="co-prompt" maxlength="200" autocomplete="off"> <button type="submit">Send</button></div>
|
|
<p id="co-left"></p>
|
|
</form>
|
|
<ul id="co-selections"></ul>
|
|
</div>
|
|
<div>
|
|
<h3>Fidelity</h3>
|
|
<ul id="co-fidelity"></ul>
|
|
</div>
|
|
</div>
|
|
<div id="co-lead" class="controls" hidden>
|
|
<label for="co-turn-s">Turn (s)</label> <input id="co-turn-s" type="number" min="1" max="60" value="4">
|
|
<label for="co-turns-n">Turns</label> <input id="co-turns-n" type="number" min="1" max="64" value="4">
|
|
<label for="co-mode">Out of turn</label> <select id="co-mode"><option value="mark">marked</option><option value="refuse">refused</option></select>
|
|
<button id="co-start" type="button">Start turns</button>
|
|
<label for="co-q">Ask</label> <input id="co-q" maxlength="200" placeholder="Which way next?">
|
|
<label for="co-options-n">Options</label> <input id="co-options-n" type="number" min="2" max="32" value="3">
|
|
<label for="co-window-s">Open for (s)</label> <input id="co-window-s" type="number" min="2" max="300" value="8">
|
|
<button id="co-vote" type="button">Open a vote</button>
|
|
<button id="co-prompts" type="button">Ask for prompts</button>
|
|
<button id="co-fid" type="button">State fidelity</button>
|
|
</div>
|
|
<!-- With ?debug=binding: the state of the frame each tile shows (WAI browser-sink §10). -->
|
|
<section id="binding-debug" style="grid-column: 1 / -1" hidden>
|
|
<h2>Frames on screen</h2>
|
|
<div style="overflow-x: auto"><table class="binding">
|
|
<thead><tr><th>Tile</th><th>State</th><th>Tier</th><th>Digest</th><th>Receipt</th><th>Chain</th><th>Provenance</th><th>Route</th><th>Power efficient</th><th>Energy</th></tr></thead>
|
|
<tbody></tbody>
|
|
</table></div>
|
|
</section>
|
|
<section style="grid-column: 1 / -1">
|
|
<h2>Log</h2>
|
|
<pre id="log"></pre>
|
|
</section>
|
|
</main>
|
|
<script type="module">
|
|
// The controls are for a room service alone; a fixture's page has no use for them.
|
|
if (!new URLSearchParams(location.search).get("service")) document.getElementById("room-app").hidden = true;
|
|
</script>
|
|
<script type="module" src="./app.js"></script>
|
|
</body>
|
|
</html>
|