| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="utf-8" /> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <title>SCRYPT — session</title> |
| <link rel="stylesheet" href="/static/app.css" /> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css" /> |
| </head> |
| <body> |
| <main class="room"> |
| <section class="term-frame"> |
| <div class="term-bar"> |
| <span class="dot r"></span><span class="dot y"></span><span class="dot g"></span> |
| <span class="title">warden@scryptos — tty1</span> |
| <a href="/">◄ leave</a> |
| </div> |
| <div id="terminal"></div> |
| <div class="term-status" id="status">connecting to the machine…</div> |
| </section> |
| </main> |
|
|
| <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@0.18.0/lib/addon-webgl.min.js"></script> |
| <script> |
| |
| |
| const JADE = { |
| background: "#111c18", foreground: "#c1c497", cursor: "#d7c995", |
| cursorAccent: "#111c18", selectionBackground: "#2c4a3a", |
| black: "#23372b", red: "#ff5345", green: "#549e6a", yellow: "#459451", |
| blue: "#509475", magenta: "#d2689c", cyan: "#2dd5b7", white: "#f6f5dd", |
| brightBlack: "#53685b", brightRed: "#db9f9c", brightGreen: "#9eebb3", |
| brightYellow: "#e5c736", brightBlue: "#acd4cf", brightMagenta: "#75bbb3", |
| brightCyan: "#8cd3cb", brightWhite: "#f6f5dd", |
| }; |
| |
| |
| const MIN_ROWS = 44; |
| const MIN_COLS = 110; |
| const MAX_FONT = 15; |
| const MIN_FONT = 10; |
| |
| const term = new Terminal({ |
| theme: JADE, |
| fontFamily: "'IBM Plex Mono', ui-monospace, monospace", |
| fontSize: MAX_FONT, |
| lineHeight: 1.05, |
| cursorBlink: true, |
| cursorStyle: "block", |
| allowProposedApi: true, |
| scrollback: 2000, |
| |
| |
| |
| customGlyphs: true, |
| }); |
| const fit = new FitAddon.FitAddon(); |
| term.loadAddon(fit); |
| term.open(document.getElementById("terminal")); |
| |
| |
| |
| try { |
| const webgl = new WebglAddon.WebglAddon(); |
| webgl.onContextLoss(() => webgl.dispose()); |
| term.loadAddon(webgl); |
| } catch (e) { } |
| |
| |
| |
| function fitGame() { |
| let size = MAX_FONT; |
| term.options.fontSize = size; |
| fit.fit(); |
| while ((term.rows < MIN_ROWS || term.cols < MIN_COLS) && size > MIN_FONT) { |
| size -= 1; |
| term.options.fontSize = size; |
| fit.fit(); |
| } |
| } |
| fitGame(); |
| |
| const status = document.getElementById("status"); |
| function setStatus(html, dead = false) { |
| status.innerHTML = html; |
| status.classList.toggle("dead", dead); |
| } |
| |
| const proto = location.protocol === "https:" ? "wss" : "ws"; |
| const ws = new WebSocket(`${proto}://${location.host}/pty`); |
| ws.binaryType = "arraybuffer"; |
| |
| function sendResize() { |
| if (ws.readyState === WebSocket.OPEN) { |
| |
| |
| |
| const rows = Math.max(1, term.rows - 1); |
| ws.send(JSON.stringify({ resize: [term.cols, rows] })); |
| } |
| } |
| |
| ws.onopen = () => { |
| setStatus("the Warden is <b>watching</b> · type to play"); |
| sendResize(); |
| term.focus(); |
| }; |
| ws.onmessage = (ev) => { |
| const bytes = typeof ev.data === "string" ? ev.data : new Uint8Array(ev.data); |
| term.write(bytes); |
| }; |
| ws.onclose = () => setStatus("the machine closed the session. <b>refresh to wake it again.</b>", true); |
| ws.onerror = () => setStatus("connection severed.", true); |
| |
| term.onData((data) => { |
| if (ws.readyState === WebSocket.OPEN) ws.send(new TextEncoder().encode(data)); |
| }); |
| |
| window.addEventListener("resize", () => { fitGame(); sendResize(); }); |
| const ro = new ResizeObserver(() => { fitGame(); sendResize(); }); |
| ro.observe(document.getElementById("terminal")); |
| </script> |
| </body> |
| </html> |
|
|