|
|
<!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> |