tictactoe/index.html
Parley Hatch 0fc1d32099 Make tic-tac-toe epic: per-player camps, 3 stages, containerized
Each player picks ANY avatar from a combined 16-emoji roster (8 sparkle +
8 shred); their color, move sound, and particle FX follow that avatar's CAMP,
so one nibling can rock unicorns/rainbows while the other brings skulls/metal.
A 3-way STAGE selector (Sparkle / Shred / Mixed) sets the global backdrop,
fonts, and winner vibe. Camps and stages are orthogonal, driven by CSS custom
properties (.camp-* and [data-stage]).

- Combined avatar grids for both players with a same-avatar guard
- Per-camp Web Audio SFX (sine chimes vs sawtooth power chords) + mute toggle
- Per-player glow on displays, scoreboard, and board pieces; winner screen
  adopts the winner's camp; screen shake on shred moves; themed celebration
- Persist names/avatars/stage/scores/mute via localStorage; reset-scores button
- Fix nested-scroll / clipped-top bug (center with margin:auto, not flex +
  max-height); no horizontal overflow 320px-desktop; SVG favicon
- Keyboard-navigable board, ARIA roles, live status, prefers-reduced-motion
- Containerize for OrbStack: Dockerfile (nginx:alpine), nginx.conf (gzip +
  cache/security headers), docker-compose.yml — one command on :8080

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 18:20:39 -06:00

170 lines
9.5 KiB
HTML

<!DOCTYPE html>
<html lang="en" data-stage="mixed">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ultimate Tic-Tac-Toe</title>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ctext y='.9em' font-size='90'%3E%23%EF%B8%8F%E2%83%A3%3C/text%3E%3C/svg%3E">
<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=Bebas+Neue&family=Fredoka:wght@500;600;700&family=Metal+Mania&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Animated backdrop: stars (sparkle) + embers/lightning (shred); both show on mixed -->
<div class="backdrop" aria-hidden="true">
<div class="stars"></div>
<div class="embers"></div>
<div class="lightning"></div>
</div>
<button id="muteBtn" class="icon-btn" type="button" aria-pressed="false" title="Toggle sound" aria-label="Toggle sound">
<span class="icon-on">🔊</span><span class="icon-off">🔇</span>
</button>
<div class="container">
<!-- Player Setup Screen -->
<div id="nameScreen" class="screen active">
<div class="card name-card">
<h1 class="title" id="setupTitle">Ultimate Tic-Tac-Toe</h1>
<div class="sparkle-divider"></div>
<!-- Stage (global vibe) selector -->
<div class="stage-selector">
<span class="stage-label">Choose your arena</span>
<div class="stage-options" role="radiogroup" aria-label="Choose arena / stage">
<button class="stage-option" data-stage="sparkle" role="radio" aria-checked="false" type="button">
<span class="stage-emoji"></span>
<span class="stage-name">Sparkle</span>
<span class="stage-desc">Rainbows &amp; unicorns</span>
</button>
<button class="stage-option" data-stage="mixed" role="radio" aria-checked="true" type="button">
<span class="stage-emoji">⚔️</span>
<span class="stage-name">Mixed</span>
<span class="stage-desc">Anything goes</span>
</button>
<button class="stage-option" data-stage="shred" role="radio" aria-checked="false" type="button">
<span class="stage-emoji">💀</span>
<span class="stage-name">Shred</span>
<span class="stage-desc">Skulls &amp; metal</span>
</button>
</div>
</div>
<p class="setup-hint">Each player picks <strong>any</strong> avatar — sparkle or shred, your call. 🌈🤘</p>
<div class="players-setup">
<div class="player-setup" id="p1Setup">
<div class="name-input-group">
<label for="player1Name">
<span class="player-icon p1-icon">🌟</span>
Player 1
</label>
<input type="text" id="player1Name" placeholder="Enter name..." maxlength="15" autocomplete="off">
</div>
<div class="player-avatar-group">
<span class="avatar-label">Pick an avatar</span>
<div class="avatar-options" id="player1Avatars" role="radiogroup" aria-label="Player 1 avatar"></div>
</div>
</div>
<div class="player-setup" id="p2Setup">
<div class="name-input-group">
<label for="player2Name">
<span class="player-icon p2-icon">💫</span>
Player 2
</label>
<input type="text" id="player2Name" placeholder="Enter name..." maxlength="15" autocomplete="off">
</div>
<div class="player-avatar-group">
<span class="avatar-label">Pick an avatar</span>
<div class="avatar-options" id="player2Avatars" role="radiogroup" aria-label="Player 2 avatar"></div>
</div>
</div>
</div>
<button id="startBtn" class="btn btn-primary" type="button">
<span class="btn-label">Start Battle!</span>
</button>
</div>
</div>
<!-- Game Screen -->
<div id="gameScreen" class="screen">
<div class="card game-card">
<h1 class="game-title" id="gameTitle">Tic-Tac-Toe</h1>
<div class="game-layout">
<div class="game-area">
<div class="player-info">
<div class="player-display player1-display active">
<span class="player-symbol" id="player1Symbol"></span>
<span class="player-name" id="player1Display">Player 1</span>
<div class="player-glow"></div>
</div>
<div class="vs" id="vsLabel">VS</div>
<div class="player-display player2-display">
<span class="player-symbol" id="player2Symbol">💀</span>
<span class="player-name" id="player2Display">Player 2</span>
<div class="player-glow"></div>
</div>
</div>
<div class="game-status" id="gameStatus" aria-live="polite"></div>
<div class="game-board" id="gameBoard" role="grid" aria-label="Tic-tac-toe board">
<button class="cell" data-cell data-index="0" role="gridcell" aria-label="Cell 1, empty"></button>
<button class="cell" data-cell data-index="1" role="gridcell" aria-label="Cell 2, empty"></button>
<button class="cell" data-cell data-index="2" role="gridcell" aria-label="Cell 3, empty"></button>
<button class="cell" data-cell data-index="3" role="gridcell" aria-label="Cell 4, empty"></button>
<button class="cell" data-cell data-index="4" role="gridcell" aria-label="Cell 5, empty"></button>
<button class="cell" data-cell data-index="5" role="gridcell" aria-label="Cell 6, empty"></button>
<button class="cell" data-cell data-index="6" role="gridcell" aria-label="Cell 7, empty"></button>
<button class="cell" data-cell data-index="7" role="gridcell" aria-label="Cell 8, empty"></button>
<button class="cell" data-cell data-index="8" role="gridcell" aria-label="Cell 9, empty"></button>
</div>
<div class="button-group">
<button id="resetBtn" class="btn btn-ghost" type="button">🔄 New Round</button>
<button id="changeNamesBtn" class="btn btn-ghost" type="button">👥 Change Players</button>
</div>
</div>
<div class="score-sidebar">
<h2 class="score-title" id="scoreTitle">Scoreboard</h2>
<div class="score-item player1-score">
<span class="score-avatar" id="score1Avatar"></span>
<span class="score-name" id="score1Name">Player 1</span>
<span class="score-value" id="score1">0</span>
</div>
<div class="score-divider" id="scoreDivider">🏆</div>
<div class="score-item player2-score">
<span class="score-avatar" id="score2Avatar">💀</span>
<span class="score-name" id="score2Name">Player 2</span>
<span class="score-value" id="score2">0</span>
</div>
<button id="resetScoresBtn" class="btn btn-tiny" type="button">Reset Scores</button>
</div>
</div>
</div>
</div>
<!-- Winner Screen -->
<div id="winnerScreen" class="screen">
<div class="card winner-card">
<div class="celebration" id="celebration" aria-hidden="true"></div>
<h1 class="winner-title" id="winnerTitle">Winner!</h1>
<div class="trophy" id="winnerAvatar">🏆</div>
<p class="winner-name" id="winnerName"></p>
<p class="winner-sub" id="winnerSub"></p>
<div class="winner-buttons">
<button id="playAgainBtn" class="btn btn-primary" type="button">Play Again!</button>
<button id="changeNamesBtn2" class="btn btn-ghost" type="button">Change Players</button>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>