File size: 3,461 Bytes
720ecf7 5e45c59 720ecf7 91e6663 720ecf7 91e6663 720ecf7 91e6663 720ecf7 91e6663 720ecf7 91e6663 720ecf7 d3d402f 5e45c59 d3d402f 5e45c59 d3d402f 720ecf7 d3d402f 720ecf7 91e6663 5e45c59 91e6663 5e45c59 91e6663 5e45c59 91e6663 5e45c59 91e6663 5e45c59 91e6663 5e45c59 91e6663 5e45c59 720ecf7 5e45c59 d3d402f 5e45c59 720ecf7 5e45c59 720ecf7 d3d402f 720ecf7 d3d402f 5e45c59 d3d402f 5e45c59 720ecf7 91e6663 720ecf7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
<!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"; // authoritative client state
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(){
// Single initial state fetch. After that, trust MODE from the last successful response.
render(await fetchState());
btn.addEventListener("click", async () => { await callPrimary(); });
// Enter uses local MODE. No pre-Enter GET /state (prevents the /primary loop).
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> |