Scoring bug (priority fix): MultiplayerMatch/BotMatch add a `hidden` class to the solo result panel (#solo-score-meter-wrap, #score-verdict, #score-breakdown, #compare-toggle) once a networked/bot round's real two-column scoreboard takes over, but no CSS rule ever paired `.hidden` with those selectors (every other hideable element in this codebase has its own scoped rule) -- the class was inert, so the solo panel stayed visible forever, stacked above the mp-scoreboard, making both look like they showed the same/wrong numbers. Added the missing scoped rules. Reveal button (online play): clicking Reveal Match used to run ThemeGuessGame's own instant local reveal() even in a live multiplayer round -- unrelated to the server, and the direct trigger of the above scoring bug. Added a revealOverride hook on ThemeGuessGame; while a multiplayer round is live, MultiplayerMatch routes the click through a new round:revealVote message instead. The round now only ends early once *both* players vote (server tracks votes per session and reveals immediately on a mutual vote); a single vote relays round:revealVoteUpdate to the opponent, and the voter sees "Waiting for opponent to vote for reveal..." below the button. Theme preview timing: matchmaking and private-room rounds never ran the 10s theme-preview flash solo/bot rounds already have, so a round's real colors never appeared before painting started. Added it to both flows (ui/matchmakingFlow.ts, ui/privateRoomFlow.ts, including every match in a private room's up-to-5-match series, not just the first). The backend's round:start.endsAt now bakes in a matching 10s PREVIEW_MS head start (session.js) so the preview never eats into actual painting time. Private room results view: a private room's next match used to start within milliseconds of the previous one's reveal, cutting the result modal off almost instantly. room.js now delays round:nextMatch (and the series-ending room:closed) by RESULT_VIEW_MS (10s) after round:reveal so both players actually get to look at a match's result. Theme grid / vote UI: the theme-vote modal's grid was missing the theme-grid class that actually applies the 2-column CSS grid (dead CSS already anticipated the combination), so vote theme cards rendered in whatever the browser's default block flow was. Added the class, and added a distinct (pink, dashed outline) opponent-pick highlight to ui/themeGrid.ts so the opponent's live vote pick is visually distinct from the local player's own selection, keeping the existing 'Opponent chose <theme>' text. PROTOCOL.md updated for all wire-visible changes: endsAt formula, round:revealVote/round:revealVoteUpdate, and the round:nextMatch/ room:closed timing.
218 lines
9.4 KiB
HTML
218 lines
9.4 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Theme Guess — Pixel Palette</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet" />
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<header class="topbar">
|
|
<div class="brand">
|
|
<span class="brand-swatches">
|
|
<i style="background:#fb4934"></i><i style="background:#b8bb26"></i>
|
|
<i style="background:#fabd2f"></i><i style="background:#83a598"></i>
|
|
</span>
|
|
<h1>Theme<span>Guess</span></h1>
|
|
</div>
|
|
|
|
<div id="solo-timer-mount"></div>
|
|
<button id="help-btn" class="theme-badge" title="Change theme / how to play">
|
|
🎮 <strong id="theme-name-badge">Gruvbox Dark</strong>
|
|
</button>
|
|
<div class="topbar-actions">
|
|
<button id="mute-btn" class="icon-btn" title="Mute sound" aria-label="Mute sound">🔊</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="progress-wrap">
|
|
<div class="progress-track"><div id="progress-fill" class="progress-fill"></div></div>
|
|
<span id="progress-label">0 / 12 TOKEN KINDS THEMED</span>
|
|
</div>
|
|
|
|
<div class="match-hud-row">
|
|
<div id="match-timer-hud" class="hidden"></div>
|
|
<button id="match-quit-btn" class="btn danger small hidden" title="Quit this match">✕ Quit</button>
|
|
</div>
|
|
<div id="match-timer-warning" class="hidden"></div>
|
|
|
|
<main class="board">
|
|
<div id="split-view" class="split-pane hidden" aria-label="Opponent progress">
|
|
<div class="split-pane-label">Opponent</div>
|
|
<div class="canvas-wrap split-canvas-wrap">
|
|
<canvas id="opponent-canvas" class="split-canvas"></canvas>
|
|
<div id="opponent-complete-banner" class="opponent-complete-banner hidden">🏁 Opponent finished!</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="split-pane yours-pane">
|
|
<div class="split-pane-label yours-label">Yours</div>
|
|
<div class="canvas-wrap split-canvas-wrap">
|
|
<canvas id="code-canvas"></canvas>
|
|
|
|
<div id="picker-popover" class="popover hidden">
|
|
<div class="popover-arrow"></div>
|
|
<div class="popover-label" id="popover-label">Keyword</div>
|
|
<div class="popover-row">
|
|
<input type="color" id="popover-color" value="#ffffff" />
|
|
<input type="text" id="popover-hex" maxlength="7" spellcheck="false" autocomplete="off" />
|
|
</div>
|
|
<div class="popover-actions">
|
|
<button id="popover-cancel" class="btn ghost small">Cancel</button>
|
|
<button id="popover-apply" class="btn primary small">Apply</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<aside class="panel-col" aria-label="Token kinds">
|
|
<div id="category-panel" class="panel"></div>
|
|
<div class="panel-actions">
|
|
<button id="reset-btn" class="btn ghost">↺ RESET COLORS</button>
|
|
<button id="reveal-btn" class="btn reveal" disabled>REVEAL MATCH ✨</button>
|
|
</div>
|
|
<p id="reveal-vote-status" class="reveal-vote-status hidden"></p>
|
|
</aside>
|
|
</main>
|
|
</div>
|
|
|
|
<div id="intro-modal" class="modal-overlay">
|
|
<div class="modal menu-modal">
|
|
<h2>🕹️ THEME GUESS</h2>
|
|
<p>A snippet of code is hiding a classic color theme. Pick how you want to play.</p>
|
|
|
|
<div class="menu-sections">
|
|
<section id="menu-solo" class="menu-section" aria-label="Solo">
|
|
<h3>🧑💻 SOLO</h3>
|
|
<p class="menu-section-desc">Click any word to color it, then reveal how close your palette lands to the real theme.</p>
|
|
|
|
<div class="solo-config-group">
|
|
<p class="solo-config-label">⏱ TIME</p>
|
|
<div class="solo-radio-row">
|
|
<label class="solo-radio"><input type="radio" name="solo-time-mode" value="2min" checked />2 MIN</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-time-mode" value="4min" />4 MIN</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-time-mode" value="none" />NO LIMIT</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="solo-config-group">
|
|
<p class="solo-config-label">🎨 THEME</p>
|
|
<div class="solo-radio-row">
|
|
<label class="solo-radio"><input type="radio" name="solo-theme-mode" value="random" checked />RANDOM</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-theme-mode" value="choose" />CHOOSE</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="solo-config-group">
|
|
<p class="solo-config-label">🤖 OPPONENT</p>
|
|
<div class="solo-radio-row">
|
|
<label class="solo-radio"><input type="radio" name="solo-opponent-mode" value="alone" checked />ALONE</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-opponent-mode" value="bot" />VS BOT</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="solo-bot-difficulty" class="hidden">
|
|
<p class="solo-config-label">DIFFICULTY</p>
|
|
<div class="solo-radio-row">
|
|
<label class="solo-radio"><input type="radio" name="solo-bot-difficulty" value="easy" checked />EASY</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-bot-difficulty" value="medium" />MEDIUM</label>
|
|
<label class="solo-radio"><input type="radio" name="solo-bot-difficulty" value="hard" />HARD</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="menu-section-actions">
|
|
<button id="start-btn" class="btn primary">▶ PLAY</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="menu-private" class="menu-section" aria-label="Private">
|
|
<h3>🔒 PRIVATE</h3>
|
|
<p class="menu-section-desc">Play with friends in a private room, by invite code or a fresh room.</p>
|
|
<div class="menu-section-actions">
|
|
<button id="enter-code-btn" class="btn ghost">Enter Code</button>
|
|
<button id="create-room-btn" class="btn ghost">Create Room</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="menu-matchmaking" class="menu-section" aria-label="Matchmaking">
|
|
<h3>🌐 MATCHMAKING</h3>
|
|
<p class="menu-section-desc">Get paired with another player looking for a match right now.</p>
|
|
<div class="menu-section-actions">
|
|
<button id="find-match-btn" class="btn primary">Find Match</button>
|
|
</div>
|
|
<div id="matchmaking-status" class="hidden"></div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="result-modal" class="modal-overlay hidden">
|
|
<div class="modal result-modal">
|
|
<h2>MATCH RESULT</h2>
|
|
<div class="score-meter-wrap" id="solo-score-meter-wrap">
|
|
<div class="score-meter-number" id="score-number">0%</div>
|
|
<div class="score-meter-track" id="score-meter"></div>
|
|
</div>
|
|
<div id="score-verdict" class="verdict"></div>
|
|
<div id="score-breakdown" class="breakdown"></div>
|
|
<div class="compare-toggle" id="compare-toggle">
|
|
<button data-mode="yours" class="toggle-btn active">Your Palette</button>
|
|
<button data-mode="original" class="toggle-btn">Original</button>
|
|
</div>
|
|
<div id="mp-scoreboard" class="mp-scoreboard hidden">
|
|
<div id="mp-winner-banner" class="mp-winner-banner"></div>
|
|
<div class="mp-columns">
|
|
<div class="mp-column">
|
|
<div class="mp-column-label">You</div>
|
|
<div class="score-meter-wrap">
|
|
<div class="score-meter-number" id="mp-you-number">0%</div>
|
|
<div class="score-meter-track" id="mp-you-meter"></div>
|
|
</div>
|
|
<div class="breakdown" id="mp-you-breakdown"></div>
|
|
</div>
|
|
<div class="mp-column">
|
|
<div class="mp-column-label">Opponent</div>
|
|
<div class="score-meter-wrap">
|
|
<div class="score-meter-number" id="mp-opponent-number">0%</div>
|
|
<div class="score-meter-track" id="mp-opponent-meter"></div>
|
|
</div>
|
|
<div class="breakdown" id="mp-opponent-breakdown"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-actions">
|
|
<button id="close-result-btn" class="btn ghost">Keep Comparing</button>
|
|
<button id="back-to-menu-btn" class="btn ghost hidden">🏠 Back to Menu</button>
|
|
<button id="play-again-btn" class="btn primary">Play Again</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="theme-picker-modal" class="modal-overlay hidden">
|
|
<div class="modal">
|
|
<h2>🎨 CHOOSE A THEME</h2>
|
|
<div id="theme-picker-grid" class="theme-grid"></div>
|
|
<div class="modal-actions">
|
|
<button id="theme-picker-cancel-btn" class="btn ghost">Cancel</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="preview-overlay" class="modal-overlay hidden">
|
|
<div class="preview-box">
|
|
<div class="preview-countdown" id="preview-countdown">5</div>
|
|
<div class="preview-caption" id="preview-caption">MEMORIZE THE VIBE...</div>
|
|
<div class="preview-canvas-wrap">
|
|
<canvas id="preview-canvas"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="private-room-mount" class="modal-overlay hidden"></div>
|
|
|
|
<script type="module" src="/src/main.ts"></script>
|
|
</body>
|
|
</html>
|