My previous 'no scrolling' commit didn't actually work — two real bugs: 1. Stale assets: nginx cached css/js for a week (max-age=604800), so after a redeploy returning visitors kept the OLD stylesheet/script and never saw the fit logic. Switched assets to Cache-Control:no-cache (tiny files, 304s) and added ?v= query strings to bust the existing week-long cache. 2. Bad centering geometry: grid-centering pins an over-tall card to the top, and .winner-card re-declared position:relative, overriding the fix. Now the card is position:fixed at 50%/50% and fitActiveScreen() folds the fit scale into one transform: translate(-50%,-50%) scale(fit). Removed the winner-card position override and switched the screen fade to opacity-only (a transform on .screen would capture the fixed card and break centering). Verified in real Chromium against the container — setup, game, AND winner all show fits:true / scrolls:false at 1366x640 (short laptop), 375x667, and 360x640 (small phone). No scrolling on any screen at any size. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
170 lines
9.5 KiB
HTML
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?v=5">
|
|
</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 & 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 & 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?v=5"></script>
|
|
</body>
|
|
</html>
|