| <!doctype html> |
| <html> |
| <head> |
| <meta charset="utf-8" /> |
| <title>Room Scenario Game</title> |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> |
| <style> |
| :root { --w: 1000px; } |
| body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 16px; } |
| #wrap { max-width: var(--w); margin: 0 auto; } |
| #log { width: 100%; height: 60vh; border: 1px solid #ccc; padding: 12px; overflow: auto; white-space: pre-wrap; } |
| #row { margin-top: 12px; display: flex; gap: 8px; } |
| #cmd { flex: 1; padding: 8px; font-size: 16px; } |
| button { padding: 8px 12px; font-size: 16px; } |
| #small { color: #666; font-size: 12px; margin-top: 6px; } |
| </style> |
| </head> |
| <body> |
| <div id="wrap"> |
| <div id="log"></div> |
| <div id="row"> |
| <input id="cmd" type="text" placeholder="[Press Enter to start]" autocomplete="off" /> |
| <button id="primary">Start</button> |
| </div> |
| <div id="small">Enter starts, continues, or submits your move. The button does the same.</div> |
| </div> |
|
|
| <script> |
| const log = document.getElementById("log"); |
| const cmd = document.getElementById("cmd"); |
| const btn = document.getElementById("primary"); |
| |
| let SID = ""; |
| let MODE = "awaiting_start"; |
| |
| function scrollBottom(){ log.scrollTop = log.scrollHeight; } |
| |
| function render(s){ |
| if (s.sid) SID = s.sid; |
| if (s.mode) MODE = s.mode; |
| log.textContent = s.transcript || ""; |
| btn.textContent = s.primary_label || "Start"; |
| cmd.placeholder = s.placeholder || ""; |
| btn.style.display = (MODE === "awaiting_action") ? "none" : "inline-block"; |
| cmd.focus(); |
| scrollBottom(); |
| } |
| |
| async function fetchState(){ |
| const r = await fetch(SID ? `/state?sid=${SID}` : "/state"); |
| if (!r.ok) throw new Error("state fetch failed"); |
| return r.json(); |
| } |
| async function callPrimary(){ |
| btn.disabled = true; |
| try{ |
| const r = await fetch(SID ? `/primary?sid=${SID}` : "/primary", { method: "POST" }); |
| render(await r.json()); |
| cmd.value = ""; |
| } finally { btn.disabled = false; } |
| } |
| async function callAction(text){ |
| btn.disabled = true; |
| try{ |
| const r = await fetch(SID ? `/action?sid=${SID}` : "/action", { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ text }) |
| }); |
| render(await r.json()); |
| cmd.value = ""; |
| } finally { btn.disabled = false; } |
| } |
| |
| async function init(){ |
| |
| render(await fetchState()); |
| |
| btn.addEventListener("click", async () => { await callPrimary(); }); |
| |
| |
| cmd.addEventListener("keydown", async (e) => { |
| if (e.key !== "Enter") return; |
| e.preventDefault(); |
| if (MODE === "awaiting_action"){ |
| await callAction(cmd.value.trim()); |
| } else if (MODE === "awaiting_continue" || MODE === "awaiting_start"){ |
| await callPrimary(); |
| } |
| }); |
| |
| cmd.focus(); |
| scrollBottom(); |
| } |
| init(); |
| </script> |
| </body> |
| </html> |