theme_guess/frontend/index.html
Gabriel Franco 5190ec19a3 feat(frontend): add in-match orchestrator and multiplayer scoreboard
New multiplayerMatch.ts (MultiplayerMatch class) drives one round: syncs
ThemeGuessGame/OpponentView to a round:start payload, relays local
category assignments as round:progress, forwards opponent round:progress
to OpponentView, drives timerHud's countdown (submitting the local color
map as round:submit at expiry), and renders a two-column scoreboard on
round:reveal via computeMatchResult.

ThemeGuessGame gains an optional onCategoryAssigned?(id, hex) constructor
callback, invoked as the last step of applyColor.

timerHud's TimerHudOptions gains an optional onExpire?() callback on the
countdown variant, fired once when endsAt passes.

resultView.ts gains renderMultiplayerResult() for the two-column
you/opponent scoreboard + winner banner (equal overall -> explicit draw).

index.html/style.css: additive #mp-scoreboard/#mp-winner-banner/#mp-you-*/
#mp-opponent-* markup and .mp-* styles; existing single-player result
markup untouched aside from adding id="solo-score-meter-wrap" so it can
be toggled off during a multiplayer reveal.
2026-09-10 12:16:15 -03:00

210 lines
8.9 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 id="match-timer-hud" class="hidden"></div>
<div id="match-timer-warning" class="hidden"></div>
<main class="board">
<div class="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>
<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>
</aside>
</main>
<section id="split-view" class="split-view hidden" aria-label="Match progress">
<div class="split-pane">
<div class="split-pane-label">Yours</div>
<div class="canvas-wrap split-canvas-wrap">
<canvas id="split-your-canvas" class="split-canvas"></canvas>
</div>
</div>
<div class="split-pane">
<div class="split-pane-label">Opponent</div>
<div class="canvas-wrap split-canvas-wrap">
<canvas id="opponent-canvas" class="split-canvas"></canvas>
</div>
</div>
</section>
</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="1min" checked />1 MIN</label>
<label class="solo-radio"><input type="radio" name="solo-time-mode" value="2min" />2 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 id="solo-theme-picker" class="hidden">
<p class="pick-theme-label">▼ PICK A THEME ▼</p>
<div id="theme-grid" class="theme-grid"></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>
</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="change-theme-btn" class="btn ghost">🔄 Change Theme</button>
<button id="play-again-btn" class="btn primary">Play Again</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>
<script type="module" src="/src/main.ts"></script>
</body>
</html>