const root = document.querySelector(".play-layout"); const sessionId = root.dataset.session; const token = root.dataset.token; const isHost = root.dataset.host === "1"; const playerId = Number(root.dataset.player); let roundSeen = 0; const playersEl = document.getElementById("players"); const stageEl = document.getElementById("stage"); const tipEl = document.getElementById("tip"); const inviteEl = document.getElementById("invite"); const hostForm = document.getElementById("host-form"); const startBtn = document.getElementById("start-btn"); const nextBtn = document.getElementById("next-btn"); inviteEl.textContent = "Invite: " + location.origin + "/join/" + sessionId; let lastChoice = null; let submitted = false; let proto = location.protocol === "https:" ? "wss" : "ws"; const ws = new WebSocket(`${proto}://${location.host}/ws/${sessionId}?token=${token}`); ws.onmessage = (ev) => { const data = JSON.parse(ev.data); if (data.type === "error") { tipEl.textContent = data.message; return; } if (data.type === "state") render(data); }; function send(payload) { ws.send(JSON.stringify(payload)); } if (startBtn) startBtn.onclick = () => send({ action: "start" }); if (nextBtn) nextBtn.onclick = () => send({ action: "next" }); function render(data) { const s = data.session; playersEl.innerHTML = data.players .map( (p) => `
  • ${escapeHtml(p.name)} — ${p.score} pts
  • ` ) .join(""); if (hostForm) hostForm.hidden = s.status !== "lobby"; if (startBtn) startBtn.hidden = s.status !== "lobby"; if (nextBtn) nextBtn.hidden = !(s.status === "playing" && data.round && data.round.status === "revealed"); if (s.status === "lobby") { submitted = false; lastChoice = null; tipEl.textContent = "Lobby time! Host tweaks the rules, then hits START. Share the invite link."; stageEl.innerHTML = `

    Waiting for players (${data.players.length}/${s.max_players}). Rounds: ${s.num_rounds}.

    `; return; } if (s.status === "finished") { tipEl.textContent = `Match over! ${data.winner} takes the crown. High score wins the RAL throne.`; stageEl.innerHTML = `

    FINAL BOARD

    ` + playersEl.outerHTML; return; } const r = data.round; if (!r) return; if (r.number !== roundSeen) { roundSeen = r.number; lastChoice = null; submitted = false; } if ((r.answered_ids || []).includes(playerId)) submitted = true; if (r.status === "open") { if (r.bonus) { tipEl.textContent = "BONUS ROUND! Harder posting. 150 for the right band, 300 if you nail the exact RAL, -50 if the exact guess is wrong. Also pick hybrid / remote / in-place (+50)."; } else { tipEl.textContent = "Read the offer. Pick the closest RAL band. 100 points if you hit it, 0 if not. Optional exact number: +200 or -50."; } } else { tipEl.textContent = "Everyone locked in. Here's the reveal — host can move to the next round."; } const job = r.job; const meta = []; if (r.bonus) meta.push(`BONUS ROUND`); meta.push(`ROUND ${r.number}/${s.num_rounds}`); if (job.company) meta.push(`${escapeHtml(job.company)}`); if (job.subject) meta.push(`${escapeHtml(job.subject)}`); if (job.location) meta.push(`${escapeHtml(job.location)}`); if (job.extra) meta.push(`${escapeHtml(job.extra)}`); const locked = r.status !== "open" || submitted; const options = r.options .map((o) => { let cls = "opt"; if (lastChoice === o.key) cls += " picked"; if (r.status === "revealed") { if (o.key === r.correct) cls += " good"; else if (lastChoice === o.key) cls += " bad"; } return ``; }) .join(""); let extra = ""; if (job.has_exact) { extra += ``; } if (r.bonus) { extra += ``; } let results = ""; if (r.status === "revealed") { const ral = r.ral_exact != null ? `Exact RAL: ${r.ral_exact}` : `RAL range: ${r.ral_min} - ${r.ral_max}`; results = `

    ${ral} · Company: ${escapeHtml(r.company)} · Work: ${escapeHtml(r.work_type)}

    `; } else { results = `

    Locked in: ${r.answered.join(", ") || "nobody yet"}. Waiting: ${r.waiting.join(", ")}

    `; } stageEl.innerHTML = `

    ${escapeHtml(job.title)}

    ${meta.join("")}
    ${escapeHtml(job.description)}
    ${options}
    ${extra} ${r.status === "open" ? `` : ""} ${results} `; stageEl.querySelectorAll(".opt").forEach((btn) => { btn.onclick = () => { if (locked) return; lastChoice = btn.dataset.key; stageEl.querySelectorAll(".opt").forEach((b) => b.classList.remove("picked")); btn.classList.add("picked"); }; }); const lockBtn = document.getElementById("lock-btn"); if (lockBtn) { lockBtn.onclick = () => { if (!lastChoice) { tipEl.textContent = "Pick a RAL band first, then lock it in."; return; } const preciseEl = document.getElementById("precise"); const workEl = document.getElementById("work-type"); submitted = true; send({ action: "answer", choice: lastChoice, precise: preciseEl && preciseEl.value ? Number(preciseEl.value) : null, work_type: workEl ? workEl.value : "", }); }; } } function escapeHtml(str) { return String(str || "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); }