// Undercooked — the result screen (phase 07 task 02). // // The verdict, as the `round` frame carries it: the result word, the // stars (1–3, the pop is decorative and `prefers-reduced-motion`-gated), // score against target, the session's best for the level that was // played, the per-player contribution list (display only — PLAN §5: // never competitive gatekeeping), the counters (`served` / `missed` / // `wasted`, the longest streak), and the three exits — *Next level* // (only when `round.unlocked` names a level), *Retry* (`next_level`) // and *Back to lobby* (`to_lobby`). The outcome is announced to screen // readers through an `aria-live="polite"` region. import { el, frag } from "./dom.js"; let bestEl = null; let bannerEl = null; function starsFor(stars) { const count = Math.max(0, Math.min(3, stars)); const spans = []; for (let i = 0; i < 3; i += 1) { spans.push( el("span", { class: i < count ? "star filled" : "star", "aria-hidden": "true" }, i < count ? "★" : "☆"), ); } return el("p", { id: "stars", class: "stars", "aria-label": `${count} of 3 stars`, }, frag(...spans)); } function resultWord(result) { if (result === "won") return "Won"; if (result === "lost") return "Lost"; if (result === "aborted") return "Aborted"; return "Round over"; } function announcement(result, score, target, stars) { const word = result === "won" ? "won" : result === "lost" ? "lost" : "ended"; return `You ${word} ${score} of ${target} points with ${stars} star${stars === 1 ? "" : "s"}.`; } export function mount(target, _data, app) { const round = app.round ?? {}; const result = round.result ?? null; const stars = round.stars ?? 0; const score = round.score ?? 0; const targetScore = round.target ?? 0; const players = (round.players ?? []).map((player) => el("li", { class: "result-player" }, el("span", { class: "seat-name" }, player.name), el("span", {}, `${player.score} pts · ${player.dishes_served} dish${player.dishes_served === 1 ? "" : "es"}`), ), ); const counters = round.counters ?? {}; bestEl = el("p", { id: "best-line" }, "Best: loading…"); bannerEl = el("p", { class: "hint", id: "result-banner", hidden: true, role: "status", "aria-live": "polite" }); const actions = el("div", { class: "result-actions" }, el("button", { id: "retry-btn", class: "btn", type: "button", onClick: () => app.nextLevel(), }, "Retry"), el("button", { id: "to-lobby-btn", class: "btn btn-primary", type: "button", onClick: () => app.toLobby(), }, "Back to lobby"), ); if (round.unlocked !== null && round.unlocked !== undefined) { actions.prepend( el("button", { id: "next-level-btn", class: "btn btn-primary", type: "button", onClick: () => app.nextLevel(), }, "Next level"), ); } target.append( el("div", { class: "panel result" }, el("h2", { id: "result-word" }, resultWord(result)), starsFor(stars), el("p", { id: "score-line" }, `Score ${score} of ${targetScore}`), bestEl, el("h3", { class: "section-label" }, "Cooks"), players.length === 0 ? el("p", { class: "hint" }, "no players") : el("ul", { id: "result-players", class: "seat-list" }, frag(...players)), el("p", { id: "counters-line" }, `Served ${counters.served ?? 0} · Missed ${counters.missed ?? 0} · Wasted ${counters.wasted ?? 0} · Longest streak ${round.longest_streak ?? 0}`), actions, bannerEl, ), el("p", { id: "result-announce", class: "visually-hidden", role: "status", "aria-live": "polite", }, announcement(result, score, targetScore, stars)), ); // The best for the level that was played (the session update lands // with the `round` frame — `round_over` records before it broadcasts). void app.fetchSession().then(() => { if (bestEl !== null) renderBest(app); }); } export function renderBest(app) { if (bestEl === null) return; const round = app.round ?? {}; const session = app.session; const best = session !== null && round.level_id !== undefined ? session.best?.[round.level_id] : undefined; bestEl.textContent = best !== undefined ? `Best: ${best.score} pts, ${"★".repeat(best.stars)}${"☆".repeat(3 - best.stars)}` : "Best: —"; } /** Same-screen re-show: re-render the best line (a `round` re-broadcast * or a session refresh). */ export function update(_data, app) { if (app !== undefined) renderBest(app); } export function setBanner(message) { if (bannerEl === null) return; bannerEl.textContent = message ?? ""; bannerEl.hidden = message === null || message === ""; } export function unmount() { bestEl = null; bannerEl = null; }