cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

1-n-sigls
goq / portal / index.html
index.html 22.8K
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8" />5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />6 <title>Portal</title>7 <link rel="stylesheet" href="./style.css" />8</head>9<body>10 <div class="overlay-screen hidden" id="enrollment-overlay" role="dialog" aria-modal="true" aria-labelledby="enrollment-title">11 <div class="overlay-content enrollment-content">12 <h1 id="enrollment-title">mark this portal</h1>13 <p id="enrollment-desc">Use the security key once to reveal this Portal's peer ID, then create a one-time invitation on the Sigil host.</p>14 <div class="overlay-form" id="enrollment-pin-form">15 <input class="overlay-input" id="enrollment-pin" data-controller-focus data-pin-target="enrollment-pin" type="password" inputmode="numeric" maxlength="64" placeholder="PIN" autocomplete="off" />16 <button class="overlay-btn controller-pin-btn" id="controller-pin-enrollment" data-controller-focus onclick="openPinPad('enrollment-pin', this)">D-pad PIN</button>17 </div>18 <div class="overlay-actions">19 <button class="overlay-btn primary" id="derive-portal-id" data-controller-focus onclick="derivePortalIdentity()">show portal id</button>20 </div>21 <div class="enrollment-peer hidden" id="enrollment-peer-wrap">22 <span>portal peer</span>23 <output id="enrollment-peer-id"></output>24 </div>25 <div class="overlay-actions">26 <button class="overlay-btn" id="choose-invitation" data-controller-focus onclick="chooseInvitationFile()">open invitation</button>27 </div>28 <div class="overlay-status" id="enrollment-status"></div>29 <p class="controller-hint">D-pad / left stick to move · A select · B close PIN pad</p>30 </div>31 </div>3233 <div class="overlay-screen hidden" id="invitation-overlay" role="dialog" aria-modal="true" aria-labelledby="invitation-title">34 <div class="overlay-content invitation-content">35 <h1 id="invitation-title">open this rift?</h1>36 <p>Confirm the exact host, Portal peer, and controls before enrolling.</p>37 <dl class="invitation-summary">38 <div><dt>sigil</dt><dd id="invitation-host"></dd></div>39 <div><dt>portal</dt><dd id="invitation-peer"></dd></div>40 <div><dt>expires</dt><dd id="invitation-expiry"></dd></div>41 <div><dt>grants</dt><dd id="invitation-grants"></dd></div>42 </dl>43 <div class="overlay-actions">44 <button class="overlay-btn primary" id="confirm-invitation" data-controller-focus onclick="confirmInvitation()">enroll</button>45 <button class="overlay-btn" data-controller-focus onclick="cancelInvitation()">cancel</button>46 </div>47 <div class="overlay-status" id="invitation-status"></div>48 </div>49 </div>5051 <div class="overlay-screen hidden" id="reset-enrollment-overlay" role="alertdialog" aria-modal="true" aria-labelledby="reset-enrollment-title">52 <div class="overlay-content invitation-content">53 <h1 id="reset-enrollment-title">erase this portal mark?</h1>54 <p>Only continue after revoking enrollment on Sigil. Portal will forget this host and require a new invitation.</p>55 <div class="enrollment-peer">56 <span>sigil</span>57 <output id="reset-enrollment-host"></output>58 </div>59 <div class="overlay-actions">60 <button class="overlay-btn danger" id="confirm-reset-enrollment" data-controller-focus onclick="confirmEnrollmentReset()">erase enrollment</button>61 <button class="overlay-btn" data-controller-focus onclick="cancelEnrollmentReset()">keep enrollment</button>62 </div>63 <div class="overlay-status" id="reset-enrollment-status"></div>64 </div>65 </div>6667 <div class="overlay-screen hidden focus-overlay" id="focus-handoff-overlay" role="alertdialog" aria-modal="true" aria-labelledby="focus-handoff-title">68 <div class="overlay-content focus-overlay-content">69 <h1 id="focus-handoff-title">pass control?</h1>70 <p><span class="focus-peer" id="focus-handoff-requester"></span> is asking for slot 0. Your held input is neutralized before control moves.</p>71 <div class="overlay-actions">72 <button class="overlay-btn" id="deny-focus-handoff" data-controller-focus onclick="denyFocusHandoff()">keep control</button>73 <button class="overlay-btn primary" id="approve-focus-handoff" data-controller-focus onclick="approveFocusHandoff()">pass control</button>74 </div>75 <p class="controller-hint">Safe default: keep control · B dismisses the request</p>76 </div>77 </div>7879 <div class="overlay-screen hidden focus-overlay" id="focus-preempt-overlay" role="alertdialog" aria-modal="true" aria-labelledby="focus-preempt-title">80 <div class="overlay-content focus-overlay-content">81 <h1 id="focus-preempt-title">take control?</h1>82 <p>This displaces <span class="focus-peer" id="focus-preempt-holder"></span> after Sigil neutralizes every held input.</p>83 <div class="overlay-actions">84 <button class="overlay-btn" id="cancel-focus-preempt" data-controller-focus onclick="cancelFocusPreempt()">cancel</button>85 <button class="overlay-btn danger" id="confirm-focus-preempt" data-controller-focus onclick="confirmFocusPreempt()">take control</button>86 </div>87 <p class="controller-hint">Safe default: cancel · B closes</p>88 </div>89 </div>9091 <div class="overlay-screen hidden" id="tap-overlay" style="z-index: 200;">92 <div class="overlay-content">93 <div class="flow-steps" aria-hidden="true">94 <span class="flow-step">01 pin</span>95 <span class="flow-step active">02 tap</span>96 <span class="flow-step">03 play</span>97 </div>98 <svg class="pixel-key" viewBox="0 0 26 12" width="78" height="36" aria-hidden="true">99 <rect x="0" y="1" width="8" height="10" fill="var(--terracotta)" />100 <rect x="3" y="5" width="2" height="2" fill="var(--paper)" />101 <rect x="8" y="5" width="18" height="2" fill="var(--ochre)" />102 <rect x="14" y="7" width="2" height="3" fill="var(--moss)" />103 <rect x="19" y="7" width="2" height="2" fill="var(--moss)" />104 </svg>105 <h1 id="tap-overlay-title">tap key</h1>106 <p id="tap-overlay-desc">Touch the sensor on your key when it blinks.</p>107 <div class="overlay-status pending" id="tap-status">waiting...</div>108 </div>109 </div>110111 <div class="overlay-screen hidden" id="pin-pad-overlay" role="dialog" aria-modal="true" aria-labelledby="pin-pad-title">112 <div class="overlay-content pin-pad-content">113 <h1 id="pin-pad-title">enter pin</h1>114 <div class="pin-pad-display" id="pin-pad-display" aria-live="polite">empty</div>115 <p class="controller-hint">D-pad / left stick to move · A select · B close</p>116 <div class="pin-pad-grid">117 <button class="pin-pad-key" data-controller-focus data-pin-digit="1" onclick="pinPadDigit('1')">1</button>118 <button class="pin-pad-key" data-controller-focus data-pin-digit="2" onclick="pinPadDigit('2')">2</button>119 <button class="pin-pad-key" data-controller-focus data-pin-digit="3" onclick="pinPadDigit('3')">3</button>120 <button class="pin-pad-key" data-controller-focus data-pin-digit="4" onclick="pinPadDigit('4')">4</button>121 <button class="pin-pad-key" data-controller-focus data-pin-digit="5" onclick="pinPadDigit('5')">5</button>122 <button class="pin-pad-key" data-controller-focus data-pin-digit="6" onclick="pinPadDigit('6')">6</button>123 <button class="pin-pad-key" data-controller-focus data-pin-digit="7" onclick="pinPadDigit('7')">7</button>124 <button class="pin-pad-key" data-controller-focus data-pin-digit="8" onclick="pinPadDigit('8')">8</button>125 <button class="pin-pad-key" data-controller-focus data-pin-digit="9" onclick="pinPadDigit('9')">9</button>126 <button class="pin-pad-key pin-pad-action" data-controller-focus onclick="pinPadBackspace()" aria-label="Backspace">del</button>127 <button class="pin-pad-key" data-controller-focus data-pin-digit="0" onclick="pinPadDigit('0')">0</button>128 <button class="pin-pad-key pin-pad-action" data-controller-focus onclick="closePinPad()">done</button>129 </div>130 <button class="overlay-btn pin-pad-clear" data-controller-focus onclick="pinPadClear()">clear</button>131 </div>132 </div>133134 <div class="overlay-screen hidden" id="intro">135 <div class="overlay-content">136 <div class="flow-steps" aria-hidden="true">137 <span class="flow-step active">01 pin</span>138 <span class="flow-step">02 tap</span>139 <span class="flow-step">03 play</span>140 </div>141 <h1>portal</h1>142 <p id="intro-desc">enter the Portal security-key pin</p>143 <div class="dev-warning hidden" id="dev-connect-warning" role="alert">144 DEVELOPMENT DIRECT NODE<br />PASSKEY LOOKUP BYPASSED · NOT CLIENT AUTHORIZATION145 </div>146 <div class="overlay-form" id="intro-pin-form">147 <input class="overlay-input" id="intro-pin" data-controller-focus data-pin-target="intro-pin" type="password" inputmode="numeric" maxlength="64" placeholder="PIN" autocomplete="off" />148 <button class="overlay-btn controller-pin-btn" id="controller-pin-intro" data-controller-focus onclick="openPinPad('intro-pin', this)">D-pad PIN</button>149 </div>150 <div class="overlay-actions">151 <button class="overlay-btn primary" id="intro-connect" data-controller-focus onclick="introConnect()">connect</button>152 <button class="overlay-btn danger hidden" id="reset-enrollment-intro" data-controller-focus onclick="openEnrollmentReset()">reset enrollment</button>153 </div>154 <div class="overlay-status" id="intro-status"></div>155 <p class="controller-hint" id="intro-controller-hint">Controller optional · keyboard and mouse remain available</p>156 </div>157 </div>158159 <div class="topbar">160 <span class="brand">portal</span>161 <span class="dev-badge hidden" id="dev-connect-badge" title="Skips passkey identity lookup. This is not client authorization.">dev direct-node · not auth</span>162 <span class="sep">·</span>163 <div class="status-line" id="status-line">164 <span class="dot" id="status-dot"></span>165 <span id="status-text">idle</span>166 </div>167 <div class="topbar-right">168 <span class="viewer-status hidden" id="viewer-status" aria-live="polite">viewers: unavailable</span>169 <span class="controller-status" id="controller-status">controller: none</span>170 <span class="node-id" id="node-id-text"></span>171 <button class="link-btn" data-controller-focus onclick="togglePanel(true)">info</button>172 </div>173 </div>174175 <main class="main">176 <canvas id="frame-canvas"></canvas>177 <svg id="remote-pointer" class="remote-pointer" viewBox="0 0 24 32" aria-hidden="true">178 <path d="M2 1.5v25l6.2-6.1 4.2 9.7 4.4-1.9-4.2-9.5h8.8z" />179 </svg>180 <div class="placeholder" id="placeholder">no stream</div>181 </main>182183 <div class="bottombar">184 <div class="bottombar-start">185 <button class="action danger hidden" id="action-disconnect" data-controller-focus onclick="disconnect()">disconnect</button>186 </div>187 <div class="control-bar" id="control-bar">188 <button class="control-toggle" id="control-toggle" data-controller-focus onclick="toggleControl()">189 view only · input unavailable190 </button>191 <button type="button" class="control-toggle disabled" id="audio-toggle" data-controller-focus onclick="toggleAudioMute()" aria-disabled="true" aria-label="Audio unavailable. Not connected" title="Audio unavailable: not connected">192 vol off193 </button>194 </div>195 <div class="bottombar-end">196 <span class="pin-label" id="pin-label">pin</span>197 <input class="pin-input" id="pin-input" data-controller-focus data-pin-target="pin-input" type="password" inputmode="numeric" maxlength="64" placeholder="····" autocomplete="off" />198 <button class="action" id="controller-pin-main" data-controller-focus onclick="openPinPad('pin-input', this)">D-pad PIN</button>199 <span class="sep">·</span>200 <button class="action primary" id="action-connect" data-controller-focus onclick="connectHost()">connect</button>201 </div>202 </div>203204 <div class="panel-overlay" id="panel-overlay" onclick="togglePanel(false)"></div>205 <aside class="panel" id="panel" aria-label="Connection information">206 <div class="panel-header">207 <h2>client</h2>208 <button class="link-btn" id="panel-close" data-controller-focus onclick="togglePanel(false)">close</button>209 </div>210 <div class="panel-body">211 <div class="panel-section">212 <h3><span class="hash" aria-hidden="true">#</span> security key</h3>213 <div class="info-row"><span class="info-label">status</span><span class="info-value" id="fido-status">—</span></div>214 <div class="info-row"><span class="info-label">product</span><span class="info-value" id="fido-product">—</span></div>215 <div class="info-row"><span class="info-label">vid:pid</span><span class="info-value" id="fido-vidpid">—</span></div>216 <div class="info-row"><span class="info-label">ctap</span><span class="info-value" id="fido-versions">—</span></div>217 <div class="info-row"><span class="info-label">extensions</span><span class="info-value" id="fido-extensions">—</span></div>218 <div class="info-row"><span class="info-label">pin retries</span><span class="info-value" id="fido-pin">—</span></div>219 <button class="link-btn panel-action" data-controller-focus onclick="scanFido()">rescan</button>220 <button class="link-btn panel-action hidden" id="reset-enrollment" data-controller-focus onclick="openEnrollmentReset()">reset enrollment</button>221 </div>222 <div class="panel-section" id="stream-section">223 <section class="roster-section hidden" id="roster-section" aria-labelledby="roster-heading">224 <h3 id="roster-heading"><span class="hash" aria-hidden="true">#</span> viewers</h3>225 <p class="roster-summary" id="roster-summary" aria-live="polite">Roster unavailable</p>226 <ul class="roster-list" id="roster-list" aria-label="Connected viewers"></ul>227 </section>228 <h3><span class="hash" aria-hidden="true">#</span> stream</h3>229 <div class="info-row"><span class="info-label">codec</span><span class="info-value" id="stream-codec">—</span></div>230 <div class="info-row"><span class="info-label">transport</span><span class="info-value" id="stream-transport">—</span></div>231 <div class="info-row"><span class="info-label">path</span><span class="info-value" id="stream-path">—</span></div>232 <div class="info-row"><span class="info-label">RTT</span><span class="info-value" id="stream-rtt">— ms</span></div>233 <div class="info-row"><span class="info-label">network snapshot</span><span class="info-value" id="stream-network-session">unavailable</span></div>234 <div class="info-row metric-stack"><span class="info-label">media network</span><span class="info-value" id="stream-network-media">unavailable</span></div>235 <div class="info-row metric-stack"><span class="info-label">input network</span><span class="info-value" id="stream-network-input">unavailable</span></div>236 <div class="info-row metric-stack"><span class="info-label">audio network</span><span class="info-value" id="stream-network-audio">unavailable</span></div>237 <div class="info-row metric-stack"><span class="info-label">input ACK</span><span class="info-value" id="stream-network-input-ack">unavailable</span></div>238 <div class="info-row metric-stack"><span class="info-label">adaptive feedback</span><span class="info-value" id="stream-adaptive-feedback">unavailable</span></div>239 <div class="info-row metric-stack"><span class="info-label">adaptive decision</span><span class="info-value" id="stream-adaptive-decision">unavailable</span></div>240 <div class="info-row"><span class="info-label">control</span><span class="info-value" id="stream-control">—</span></div>241 <div class="info-row"><span class="info-label">audio</span><span class="info-value" id="stream-audio-state">unavailable</span></div>242 <div class="info-row"><span class="info-label">audio webview received</span><span class="info-value" id="stream-audio-webview-received-packets">0 packets</span></div>243 <div class="info-row"><span class="info-label">audio transport missing</span><span class="info-value" id="stream-audio-transport-missing-packets">0 packets</span></div>244 <div class="info-row"><span class="info-label">audio IPC dropped</span><span class="info-value" id="stream-audio-ipc-dropped-packets">0 packets</span></div>245 <div class="info-row"><span class="info-label">audio decoder dropped</span><span class="info-value" id="stream-audio-decoder-dropped-packets">0 packets</span></div>246 <div class="info-row"><span class="info-label">audio PCM handoff dropped</span><span class="info-value" id="stream-audio-pcm-handoff-dropped-blocks">0 blocks</span></div>247 <div class="info-row"><span class="info-label">audio ring overflow</span><span class="info-value" id="stream-audio-ring-overflow-ms">0.0 ms</span></div>248 <div class="info-row"><span class="info-label">audio ring recovery discarded</span><span class="info-value" id="stream-audio-ring-recovery-discarded-ms">0.0 ms</span></div>249 <div class="info-row"><span class="info-label">audio ring buffer</span><span class="info-value" id="stream-audio-ring-buffer-ms">0.0 ms</span></div>250 <div class="info-row"><span class="info-label">audio underruns</span><span class="info-value" id="stream-audio-underruns">0 events / 0.0 ms</span></div>251 <div class="info-row"><span class="info-label">audio output silence</span><span class="info-value" id="stream-audio-silence-ms">0.0 ms</span></div>252 <div class="info-row"><span class="info-label">transport fps</span><span class="info-value" id="stream-transport-fps">0.0</span></div>253 <div class="info-row"><span class="info-label">IPC send fps</span><span class="info-value" id="stream-ipc-send-fps">0.0</span></div>254 <div class="info-row metric-stack"><span class="info-label">transport interval p50/p95/max</span><span class="info-value" id="stream-transport-interval">—</span></div>255 <div class="info-row metric-stack"><span class="info-label">IPC send duration p50/p95/max</span><span class="info-value" id="stream-ipc-send-duration">—</span></div>256 <div class="info-row"><span class="info-label">Rust timing window/cap</span><span class="info-value" id="stream-rust-timing-window">—</span></div>257 <div class="info-row metric-stack"><span class="info-label">IPC frame queue</span><span class="info-value" id="stream-frontend-queue">—</span></div>258 <div class="info-row metric-stack"><span class="info-label">IPC resync</span><span class="info-value" id="stream-frontend-resync">—</span></div>259 <div class="info-row"><span class="info-label">webview fps</span><span class="info-value" id="stream-frontend-fps">0.0</span></div>260 <div class="info-row"><span class="info-label">decode fps</span><span class="info-value" id="stream-decode-fps">0.0</span></div>261 <div class="info-row"><span class="info-label">decoder output fps</span><span class="info-value" id="stream-decoder-output-fps">0.0</span></div>262 <div class="info-row"><span class="info-label">present fps</span><span class="info-value" id="stream-present-fps">0.0</span></div>263 <div class="info-row metric-stack"><span class="info-label">A−V skew p50/p95/max absolute</span><span class="info-value" id="stream-av-skew">—</span></div>264 <div class="info-row"><span class="info-label">decode queue</span><span class="info-value" id="stream-decode-queue">0</span></div>265 <div class="info-row"><span class="info-label">present queue</span><span class="info-value" id="stream-present-queue">0</span></div>266 <div class="info-row"><span class="info-label">decode p50 / p95</span><span class="info-value" id="stream-decode-latency">—</span></div>267 <div class="info-row"><span class="info-label">client present p50 / p95</span><span class="info-value" id="stream-present-latency">—</span></div>268 <div class="info-row"><span class="info-label">draw p50/p95/p99/max</span><span class="info-value" id="stream-draw-latency">—</span></div>269 <div class="info-row metric-stack"><span class="info-label">delivery interval p50/p95/p99/max</span><span class="info-value" id="stream-delivery-cadence">—</span></div>270 <div class="info-row metric-stack"><span class="info-label">decoder input interval p50/p95/p99/max</span><span class="info-value" id="stream-decoder-input-cadence">—</span></div>271 <div class="info-row metric-stack"><span class="info-label">decoder output interval p50/p95/p99/max</span><span class="info-value" id="stream-decoder-output-cadence">—</span></div>272 <div class="info-row metric-stack"><span class="info-label">present interval p50/p95/p99/max</span><span class="info-value" id="stream-present-cadence">—</span></div>273 <div class="info-row"><span class="info-label">total discarded</span><span class="info-value" id="stream-dropped">0 frames</span></div>274 <div class="info-row"><span class="info-label">transport sequence gap</span><span class="info-value" id="stream-transport-dropped">0 frames</span></div>275 <div class="info-row"><span class="info-label">transport object discarded</span><span class="info-value" id="stream-transport-object-dropped">—</span></div>276 <div class="info-row"><span class="info-label">late object discarded</span><span class="info-value" id="stream-transport-late-object-dropped">—</span></div>277 <div class="info-row"><span class="info-label">IPC discarded</span><span class="info-value" id="stream-frontend-dropped">0 frames</span></div>278 <div class="info-row"><span class="info-label">IPC queue discarded</span><span class="info-value" id="stream-frontend-queue-dropped">—</span></div>279 <div class="info-row"><span class="info-label">IPC resync discarded</span><span class="info-value" id="stream-frontend-resync-dropped">—</span></div>280 <div class="info-row"><span class="info-label">decoder discarded</span><span class="info-value" id="stream-decoder-dropped">0 frames</span></div>281 <div class="info-row"><span class="info-label">presenter overwrite</span><span class="info-value" id="stream-present-dropped">0 frames</span></div>282 <div class="info-row"><span class="info-label">received</span><span class="info-value" id="stream-received">0</span></div>283 <div class="info-row"><span class="info-label">decoder input</span><span class="info-value" id="stream-decoder-input">0</span></div>284 <div class="info-row"><span class="info-label">decoder output</span><span class="info-value" id="stream-decoder-output">0</span></div>285 <div class="info-row"><span class="info-label">presented</span><span class="info-value" id="stream-presented">0</span></div>286 </div>287 </div>288 </aside>289290 <script type="module" src="./main.js"></script>291</body>292</html>