Initial commit: original tic-tac-toe game

Existing 2-player hotseat game with name entry, emoji avatars,
scoreboard, sound effects, and celebration animations.
This commit is contained in:
Parley Hatch 2026-05-31 17:43:45 -06:00
commit 4b7d42dc13
5 changed files with 1388 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
# OS / editor cruft
.DS_Store
Thumbs.db
*.swp
.idea/
.vscode/
# Test / tooling artifacts
/screenshots/
*.log

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Kara's ultimate tic tac toe
## Objective
Design an ultimate browser based tic tac toe game with the following features:

153
index.html Normal file
View file

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>✨ Ultimate Tic-Tac-Toe ✨</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="stars"></div>
<div class="container">
<!-- Player Name Input Screen -->
<div id="nameScreen" class="screen active">
<div class="name-card">
<h1 class="title">✨ Ultimate Tic-Tac-Toe ✨</h1>
<div class="sparkle-divider"></div>
<div class="name-input-group">
<label for="player1Name">
<span class="player-icon">🌟</span>
Player 1 Name
</label>
<input type="text" id="player1Name" placeholder="Enter your name..." maxlength="15">
</div>
<div class="name-input-group">
<label for="player2Name">
<span class="player-icon">💫</span>
Player 2 Name
</label>
<input type="text" id="player2Name" placeholder="Enter your name..." maxlength="15">
</div>
<div class="avatar-section">
<h3 class="avatar-title">Choose Your Avatars!</h3>
<div class="avatar-selection">
<div class="player-avatar-group">
<label>Player 1</label>
<div class="avatar-options" id="player1Avatars">
<div class="avatar-option selected" data-avatar="⭐"></div>
<div class="avatar-option" data-avatar="🦄">🦄</div>
<div class="avatar-option" data-avatar="🌸">🌸</div>
<div class="avatar-option" data-avatar="🎀">🎀</div>
<div class="avatar-option" data-avatar="🦋">🦋</div>
<div class="avatar-option" data-avatar="🌈">🌈</div>
<div class="avatar-option" data-avatar="🎨">🎨</div>
<div class="avatar-option" data-avatar="🎪">🎪</div>
</div>
</div>
<div class="player-avatar-group">
<label>Player 2</label>
<div class="avatar-options" id="player2Avatars">
<div class="avatar-option" data-avatar="⭐"></div>
<div class="avatar-option" data-avatar="🦄">🦄</div>
<div class="avatar-option selected" data-avatar="🌸">🌸</div>
<div class="avatar-option" data-avatar="🎀">🎀</div>
<div class="avatar-option" data-avatar="🦋">🦋</div>
<div class="avatar-option" data-avatar="🌈">🌈</div>
<div class="avatar-option" data-avatar="🎨">🎨</div>
<div class="avatar-option" data-avatar="🎪">🎪</div>
</div>
</div>
</div>
</div>
<button id="startBtn" class="start-btn">
Start Playing! 🎮
</button>
</div>
</div>
<!-- Game Screen -->
<div id="gameScreen" class="screen">
<div class="game-card">
<h1 class="game-title">✨ Tic-Tac-Toe ✨</h1>
<div class="game-layout">
<!-- Left Side: Game Area -->
<div class="game-area">
<div class="player-info">
<div class="player-display player1-display active">
<span class="player-symbol" id="player1Symbol">X</span>
<span class="player-name" id="player1Display"></span>
<div class="player-glow"></div>
</div>
<div class="vs">VS</div>
<div class="player-display player2-display">
<span class="player-symbol" id="player2Symbol">O</span>
<span class="player-name" id="player2Display"></span>
<div class="player-glow"></div>
</div>
</div>
<div class="game-board" id="gameBoard">
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
<div class="cell" data-cell></div>
</div>
<div class="game-status" id="gameStatus"></div>
<div class="button-group">
<button id="resetBtn" class="game-btn">🔄 New Game</button>
<button id="changeNamesBtn" class="game-btn">👥 Change Names</button>
</div>
</div>
<!-- Right Side: Score Board -->
<div class="score-sidebar">
<h2 class="score-title">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">🏆</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>
</div>
</div>
</div>
</div>
<!-- Winner Screen -->
<div id="winnerScreen" class="screen">
<div class="winner-card">
<div class="confetti"></div>
<h1 class="winner-title">🎉 Winner! 🎉</h1>
<div class="trophy" id="winnerAvatar">🏆</div>
<p class="winner-name" id="winnerName"></p>
<div class="fireworks">
<div class="firework"></div>
<div class="firework"></div>
<div class="firework"></div>
</div>
<button id="playAgainBtn" class="start-btn">Play Again! 🎮</button>
<button id="changeNamesBtn2" class="game-btn">Change Names</button>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

386
script.js Normal file
View file

@ -0,0 +1,386 @@
// Game State
let currentPlayer = 'X';
let gameBoard = ['', '', '', '', '', '', '', '', ''];
let gameActive = true;
let player1Name = '';
let player2Name = '';
let player1Avatar = '⭐';
let player2Avatar = '🌸';
let player1Score = 0;
let player2Score = 0;
// DOM Elements
const nameScreen = document.getElementById('nameScreen');
const gameScreen = document.getElementById('gameScreen');
const winnerScreen = document.getElementById('winnerScreen');
const player1Input = document.getElementById('player1Name');
const player2Input = document.getElementById('player2Name');
const startBtn = document.getElementById('startBtn');
const player1Display = document.getElementById('player1Display');
const player2Display = document.getElementById('player2Display');
const gameStatus = document.getElementById('gameStatus');
const gameBoardElement = document.getElementById('gameBoard');
const cells = document.querySelectorAll('[data-cell]');
const resetBtn = document.getElementById('resetBtn');
const changeNamesBtn = document.getElementById('changeNamesBtn');
const changeNamesBtn2 = document.getElementById('changeNamesBtn2');
const playAgainBtn = document.getElementById('playAgainBtn');
const winnerName = document.getElementById('winnerName');
const winnerAvatar = document.getElementById('winnerAvatar');
// Avatar elements
const player1AvatarOptions = document.getElementById('player1Avatars');
const player2AvatarOptions = document.getElementById('player2Avatars');
// Score elements
const score1Element = document.getElementById('score1');
const score2Element = document.getElementById('score2');
const score1Name = document.getElementById('score1Name');
const score2Name = document.getElementById('score2Name');
const score1Avatar = document.getElementById('score1Avatar');
const score2Avatar = document.getElementById('score2Avatar');
// Symbol elements
const player1SymbolElement = document.getElementById('player1Symbol');
const player2SymbolElement = document.getElementById('player2Symbol');
// Winning Combinations
const winningCombinations = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6]
];
// Event Listeners
startBtn.addEventListener('click', startGame);
resetBtn.addEventListener('click', resetGame);
changeNamesBtn.addEventListener('click', showNameScreen);
changeNamesBtn2.addEventListener('click', showNameScreen);
playAgainBtn.addEventListener('click', resetGame);
cells.forEach(cell => {
cell.addEventListener('click', handleCellClick);
});
// Avatar selection listeners
player1AvatarOptions.addEventListener('click', (e) => {
if (e.target.classList.contains('avatar-option')) {
player1AvatarOptions.querySelectorAll('.avatar-option').forEach(opt => {
opt.classList.remove('selected');
});
e.target.classList.add('selected');
player1Avatar = e.target.dataset.avatar;
}
});
player2AvatarOptions.addEventListener('click', (e) => {
if (e.target.classList.contains('avatar-option')) {
player2AvatarOptions.querySelectorAll('.avatar-option').forEach(opt => {
opt.classList.remove('selected');
});
e.target.classList.add('selected');
player2Avatar = e.target.dataset.avatar;
}
});
// Allow Enter key to start game
player1Input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
player2Input.focus();
}
});
player2Input.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
startGame();
}
});
// Functions
function startGame() {
// Get player names or use defaults
player1Name = player1Input.value.trim() || 'Player 1';
player2Name = player2Input.value.trim() || 'Player 2';
// Randomly choose starting player
currentPlayer = Math.random() < 0.5 ? 'X' : 'O';
// Update displays
player1Display.textContent = player1Name;
player2Display.textContent = player2Name;
// Update symbols with avatars
player1SymbolElement.textContent = player1Avatar;
player2SymbolElement.textContent = player2Avatar;
// Update score board
score1Name.textContent = player1Name;
score2Name.textContent = player2Name;
score1Avatar.textContent = player1Avatar;
score2Avatar.textContent = player2Avatar;
score1Element.textContent = player1Score;
score2Element.textContent = player2Score;
// Switch to game screen
showGameScreen();
updateStatus();
updatePlayerHighlight();
}
function showNameScreen() {
nameScreen.classList.add('active');
gameScreen.classList.remove('active');
winnerScreen.classList.remove('active');
}
function showGameScreen() {
nameScreen.classList.remove('active');
gameScreen.classList.add('active');
winnerScreen.classList.remove('active');
}
function showWinnerScreen() {
nameScreen.classList.remove('active');
gameScreen.classList.remove('active');
winnerScreen.classList.add('active');
}
function handleCellClick(e) {
const cell = e.target;
const cellIndex = Array.from(cells).indexOf(cell);
// Check if cell is already filled or game is not active
if (gameBoard[cellIndex] !== '' || !gameActive) {
return;
}
// Make move
makeMove(cell, cellIndex);
// Check for win or draw
checkResult();
}
function makeMove(cell, index) {
gameBoard[index] = currentPlayer;
// Use avatar instead of X/O
const avatar = currentPlayer === 'X' ? player1Avatar : player2Avatar;
cell.textContent = avatar;
cell.classList.add(currentPlayer.toLowerCase());
// Create particle burst effect
createParticleBurst(cell, avatar);
// Play move sound
playMoveSound(currentPlayer);
}
function checkResult() {
let roundWon = false;
let winningCombo = null;
// Check all winning combinations
for (let i = 0; i < winningCombinations.length; i++) {
const [a, b, c] = winningCombinations[i];
if (gameBoard[a] && gameBoard[a] === gameBoard[b] && gameBoard[a] === gameBoard[c]) {
roundWon = true;
winningCombo = [a, b, c];
break;
}
}
if (roundWon) {
// Highlight winning cells
winningCombo.forEach(index => {
cells[index].classList.add('winning');
});
// Determine winner
const winner = currentPlayer === 'X' ? player1Name : player2Name;
const winnerAvatarSymbol = currentPlayer === 'X' ? player1Avatar : player2Avatar;
gameActive = false;
// Update score
if (currentPlayer === 'X') {
player1Score++;
score1Element.textContent = player1Score;
score1Element.classList.add('score-update');
setTimeout(() => score1Element.classList.remove('score-update'), 500);
} else {
player2Score++;
score2Element.textContent = player2Score;
score2Element.classList.add('score-update');
setTimeout(() => score2Element.classList.remove('score-update'), 500);
}
// Show winner screen after animation
setTimeout(() => {
winnerName.textContent = winner;
winnerAvatar.textContent = winnerAvatarSymbol;
showWinnerScreen();
playWinSound();
}, 1000);
return;
}
// Check for draw
if (!gameBoard.includes('')) {
gameActive = false;
gameStatus.textContent = "It's a Draw! 🤝";
setTimeout(() => {
resetGame();
}, 2000);
return;
}
// Switch player
currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
updateStatus();
updatePlayerHighlight();
}
function updateStatus() {
const currentPlayerName = currentPlayer === 'X' ? player1Name : player2Name;
gameStatus.textContent = `${currentPlayerName}'s Turn (${currentPlayer})`;
}
function updatePlayerHighlight() {
const player1Div = document.querySelector('.player1-display');
const player2Div = document.querySelector('.player2-display');
if (currentPlayer === 'X') {
player1Div.classList.add('active');
player2Div.classList.remove('active');
} else {
player1Div.classList.remove('active');
player2Div.classList.add('active');
}
}
function resetGame() {
// Reset game state
currentPlayer = Math.random() < 0.5 ? 'X' : 'O';
gameBoard = ['', '', '', '', '', '', '', '', ''];
gameActive = true;
// Clear cells
cells.forEach(cell => {
cell.textContent = '';
cell.classList.remove('x', 'o', 'winning');
});
// Update display
updateStatus();
updatePlayerHighlight();
// Show game screen
showGameScreen();
}
function playWinSound() {
// Create a simple celebratory sound using Web Audio API
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
// Play a series of ascending notes
const notes = [523.25, 587.33, 659.25, 783.99]; // C, D, E, G
notes.forEach((frequency, index) => {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = frequency;
oscillator.type = 'sine';
const startTime = audioContext.currentTime + (index * 0.15);
const stopTime = startTime + 0.3;
gainNode.gain.setValueAtTime(0.3, startTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, stopTime);
oscillator.start(startTime);
oscillator.stop(stopTime);
});
} catch (error) {
console.log('Audio not supported');
}
}
function playMoveSound(player) {
// Create different sound for each player
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
// Different frequencies for different players
oscillator.frequency.value = player === 'X' ? 523.25 : 659.25; // C or E
oscillator.type = 'sine';
const startTime = audioContext.currentTime;
const stopTime = startTime + 0.1;
gainNode.gain.setValueAtTime(0.2, startTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, stopTime);
oscillator.start(startTime);
oscillator.stop(stopTime);
} catch (error) {
console.log('Audio not supported');
}
}
function createParticleBurst(cell, emoji) {
// Get cell position
const rect = cell.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// Create particles
const particleCount = 8;
const particles = ['✨', '💫', '⭐', '🌟', emoji];
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.textContent = particles[Math.floor(Math.random() * particles.length)];
// Random direction
const angle = (Math.PI * 2 * i) / particleCount;
const distance = 80 + Math.random() * 40;
const tx = Math.cos(angle) * distance;
const ty = Math.sin(angle) * distance;
particle.style.left = centerX + 'px';
particle.style.top = centerY + 'px';
particle.style.setProperty('--tx', tx + 'px');
particle.style.setProperty('--ty', ty + 'px');
document.body.appendChild(particle);
// Remove particle after animation
setTimeout(() => {
particle.remove();
}, 800);
}
}
// Initialize player highlight
updatePlayerHighlight();

832
style.css Normal file
View file

@ -0,0 +1,832 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #667eea 100%);
background-size: 400% 400%;
animation: gradientShift 15s ease infinite;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow-x: hidden;
overflow-y: auto;
position: relative;
padding: 20px 0;
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Animated Stars Background */
.stars {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.stars::before,
.stars::after {
content: '';
position: absolute;
width: 2px;
height: 2px;
background: white;
box-shadow:
100px 200px white, 300px 100px white, 500px 300px white,
700px 150px white, 200px 400px white, 600px 450px white,
150px 50px white, 450px 250px white, 350px 500px white,
800px 400px white, 50px 300px white, 550px 50px white,
250px 150px rgba(255,255,255,0.5), 650px 350px rgba(255,255,255,0.5),
400px 100px rgba(255,255,255,0.5), 100px 450px rgba(255,255,255,0.5);
border-radius: 50%;
animation: twinkle 3s infinite;
}
.stars::after {
animation-delay: 1.5s;
}
@keyframes twinkle {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
.container {
width: 100%;
max-width: 900px;
padding: 20px;
position: relative;
z-index: 1;
}
/* Screens */
.screen {
display: none;
animation: fadeIn 0.5s ease;
}
.screen.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
/* Name Input Screen */
.name-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 30px;
padding: 30px 30px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.3),
0 0 40px rgba(246, 79, 254, 0.5),
inset 0 0 60px rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border: 3px solid rgba(255, 255, 255, 0.8);
max-height: 95vh;
overflow-y: auto;
}
/* Custom Scrollbar */
.name-card::-webkit-scrollbar {
width: 8px;
}
.name-card::-webkit-scrollbar-track {
background: rgba(240, 147, 251, 0.1);
border-radius: 10px;
}
.name-card::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #f093fb, #4facfe);
border-radius: 10px;
}
.name-card::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #f5576c, #f093fb);
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.title {
font-size: 2.2em;
text-align: center;
background: linear-gradient(45deg, #f093fb, #f5576c, #4facfe, #f093fb);
background-size: 200% 200%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientText 3s ease infinite;
margin-bottom: 15px;
font-weight: bold;
text-shadow: 0 0 30px rgba(245, 87, 108, 0.5);
}
@keyframes gradientText {
0%, 100% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
}
.sparkle-divider {
height: 3px;
background: linear-gradient(90deg, transparent, #f093fb, #4facfe, #f093fb, transparent);
margin: 15px 0;
border-radius: 10px;
animation: sparkleMove 2s linear infinite;
}
@keyframes sparkleMove {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.name-input-group {
margin-bottom: 20px;
}
.name-input-group label {
display: block;
margin-bottom: 10px;
font-size: 1.2em;
font-weight: bold;
color: #764ba2;
display: flex;
align-items: center;
gap: 10px;
}
.player-icon {
font-size: 1.5em;
animation: bounce 1s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
.name-input-group input {
width: 100%;
padding: 15px 20px;
font-size: 1.1em;
border: 3px solid #f093fb;
border-radius: 15px;
outline: none;
transition: all 0.3s ease;
background: rgba(255, 255, 255, 0.9);
font-family: inherit;
}
.name-input-group input:focus {
border-color: #4facfe;
box-shadow: 0 0 20px rgba(79, 172, 254, 0.5);
transform: scale(1.02);
}
/* Avatar Selection */
.avatar-section {
margin: 25px 0 15px 0;
}
.avatar-title {
text-align: center;
font-size: 1.3em;
color: #764ba2;
margin-bottom: 15px;
font-weight: bold;
}
.avatar-selection {
display: flex;
flex-direction: column;
gap: 15px;
}
.player-avatar-group label {
display: block;
font-size: 1em;
font-weight: bold;
color: #764ba2;
margin-bottom: 8px;
text-align: center;
}
.avatar-options {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.avatar-option {
aspect-ratio: 1;
font-size: 1.8em;
display: flex;
align-items: center;
justify-content: center;
border: 2px solid #e0e0e0;
border-radius: 12px;
cursor: pointer;
transition: all 0.3s ease;
background: white;
padding: 8px;
}
.avatar-option:hover {
transform: scale(1.15);
border-color: #4facfe;
box-shadow: 0 0 15px rgba(79, 172, 254, 0.5);
}
.avatar-option.selected {
border-color: #f093fb;
background: linear-gradient(135deg, rgba(240, 147, 251, 0.2), rgba(79, 172, 254, 0.2));
box-shadow: 0 0 20px rgba(240, 147, 251, 0.6);
transform: scale(1.1);
}
.avatar-option.selected:hover {
transform: scale(1.2);
}
.start-btn {
width: 100%;
padding: 15px;
font-size: 1.3em;
font-weight: bold;
border: none;
border-radius: 20px;
background: linear-gradient(135deg, #f093fb, #f5576c);
color: white;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 10px 30px rgba(245, 87, 108, 0.4);
margin-top: 15px;
}
.start-btn:hover {
transform: translateY(-5px) scale(1.05);
box-shadow: 0 15px 40px rgba(245, 87, 108, 0.6);
}
.start-btn:active {
transform: translateY(-2px) scale(1.02);
}
/* Game Screen */
.game-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 30px;
padding: 40px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.3),
0 0 40px rgba(246, 79, 254, 0.5),
inset 0 0 60px rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border: 3px solid rgba(255, 255, 255, 0.8);
}
.game-title {
font-size: 2.5em;
text-align: center;
background: linear-gradient(45deg, #f093fb, #f5576c, #4facfe);
background-size: 200% 200%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientText 3s ease infinite;
margin-bottom: 20px;
font-weight: bold;
}
/* Game Layout - Horizontal Split */
.game-layout {
display: flex;
gap: 30px;
align-items: flex-start;
}
.game-area {
flex: 1;
min-width: 0;
}
/* Score Sidebar */
.score-sidebar {
width: 220px;
flex-shrink: 0;
background: linear-gradient(135deg, rgba(240, 147, 251, 0.15), rgba(79, 172, 254, 0.15));
border-radius: 20px;
padding: 25px 20px;
border: 2px solid rgba(240, 147, 251, 0.3);
display: flex;
flex-direction: column;
gap: 20px;
}
.score-title {
text-align: center;
font-size: 1.4em;
font-weight: bold;
color: #764ba2;
margin-bottom: 10px;
}
.score-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 15px;
background: rgba(255, 255, 255, 0.5);
border-radius: 15px;
}
.score-avatar {
font-size: 3em;
animation: bounce 2s infinite;
}
.score-name {
font-size: 1em;
font-weight: bold;
color: #764ba2;
}
.score-value {
font-size: 2.5em;
font-weight: bold;
color: #f5576c;
padding: 8px 20px;
background: white;
border-radius: 15px;
border: 3px solid #f093fb;
min-width: 70px;
text-align: center;
box-shadow: 0 5px 15px rgba(240, 147, 251, 0.3);
}
.score-divider {
font-size: 2.5em;
text-align: center;
animation: rotate 3s linear infinite;
}
.score-item.player1-score .score-value {
color: #f5576c;
border-color: #f5576c;
}
.score-item.player2-score .score-value {
color: #4facfe;
border-color: #4facfe;
}
/* Player Info */
.player-info {
display: flex;
justify-content: space-around;
align-items: center;
margin-bottom: 30px;
gap: 20px;
}
.player-display {
display: flex;
flex-direction: column;
align-items: center;
padding: 15px 25px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.7);
border: 3px solid transparent;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.player-display.active {
border-color: #f093fb;
box-shadow: 0 0 30px rgba(240, 147, 251, 0.6);
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.player-glow {
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(240, 147, 251, 0.3), transparent 70%);
opacity: 0;
transition: opacity 0.3s ease;
}
.player-display.active .player-glow {
opacity: 1;
animation: rotate 3s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.player-symbol {
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
}
.player1-display .player-symbol {
color: #f5576c;
}
.player2-display .player-symbol {
color: #4facfe;
}
.player-name {
font-size: 1.1em;
font-weight: bold;
color: #764ba2;
}
.vs {
font-size: 1.5em;
font-weight: bold;
color: #764ba2;
}
/* Game Board */
.game-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 30px 0;
padding: 20px;
background: linear-gradient(135deg, rgba(240, 147, 251, 0.2), rgba(79, 172, 254, 0.2));
border-radius: 20px;
}
.cell {
aspect-ratio: 1;
background: white;
border: 4px solid #f093fb;
border-radius: 15px;
display: flex;
justify-content: center;
align-items: center;
font-size: 4em;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.cell::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(45deg, transparent, rgba(240, 147, 251, 0.3), transparent);
transform: translateX(-100%);
transition: transform 0.6s ease;
}
.cell:hover:not(.x):not(.o)::before {
transform: translateX(100%);
}
.cell:hover:not(.x):not(.o) {
transform: scale(1.1);
border-color: #4facfe;
box-shadow: 0 0 20px rgba(79, 172, 254, 0.5);
}
.cell.x,
.cell.o {
cursor: not-allowed;
}
.cell.x {
color: #f5576c;
animation: cellAppear 0.3s ease;
}
.cell.o {
color: #4facfe;
animation: cellAppear 0.3s ease;
}
@keyframes cellAppear {
from {
transform: scale(0) rotate(180deg);
opacity: 0;
}
to {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
.cell.winning {
animation: winningCell 0.6s ease infinite;
background: linear-gradient(135deg, #f093fb, #4facfe);
color: white;
}
@keyframes winningCell {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
/* Game Status */
.game-status {
text-align: center;
font-size: 1.5em;
font-weight: bold;
color: #764ba2;
min-height: 40px;
margin: 20px 0;
}
/* Buttons */
.button-group {
display: flex;
gap: 15px;
margin-top: 20px;
}
.game-btn {
flex: 1;
padding: 15px;
font-size: 1.1em;
font-weight: bold;
border: 3px solid #f093fb;
border-radius: 15px;
background: white;
color: #764ba2;
cursor: pointer;
transition: all 0.3s ease;
}
.game-btn:hover {
background: linear-gradient(135deg, #f093fb, #f5576c);
color: white;
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(240, 147, 251, 0.4);
}
/* Winner Screen */
.winner-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 30px;
padding: 60px 40px;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.3),
0 0 40px rgba(246, 79, 254, 0.5),
inset 0 0 60px rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border: 3px solid rgba(255, 255, 255, 0.8);
text-align: center;
position: relative;
overflow: hidden;
}
.winner-title {
font-size: 3em;
background: linear-gradient(45deg, #f093fb, #f5576c, #4facfe);
background-size: 200% 200%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradientText 3s ease infinite;
margin-bottom: 30px;
font-weight: bold;
}
.trophy {
font-size: 8em;
animation: trophyBounce 1s ease-in-out infinite;
margin: 20px 0;
}
@keyframes trophyBounce {
0%, 100% { transform: translateY(0) rotate(-5deg); }
50% { transform: translateY(-20px) rotate(5deg); }
}
.winner-name {
font-size: 2.5em;
font-weight: bold;
color: #764ba2;
margin: 30px 0;
text-shadow: 0 0 20px rgba(240, 147, 251, 0.5);
}
/* Confetti */
.confetti {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
}
.confetti::before,
.confetti::after {
content: '🎉 ✨ 🌟 💫 🎊 ⭐';
position: absolute;
font-size: 2em;
animation: confettiFall 3s linear infinite;
width: 100%;
text-align: center;
}
.confetti::after {
animation-delay: 1.5s;
}
@keyframes confettiFall {
0% {
top: -10%;
opacity: 1;
transform: rotate(0deg);
}
100% {
top: 110%;
opacity: 0.5;
transform: rotate(360deg);
}
}
/* Fireworks */
.fireworks {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
}
.firework {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
animation: fireworkExplode 2s ease-out infinite;
}
.firework:nth-child(1) {
top: 20%;
left: 20%;
background: #f093fb;
}
.firework:nth-child(2) {
top: 30%;
right: 20%;
background: #4facfe;
animation-delay: 0.7s;
}
.firework:nth-child(3) {
bottom: 20%;
left: 50%;
background: #f5576c;
animation-delay: 1.4s;
}
@keyframes fireworkExplode {
0% {
transform: scale(1);
opacity: 1;
box-shadow:
0 0 0 0 currentColor,
0 0 0 0 currentColor,
0 0 0 0 currentColor,
0 0 0 0 currentColor;
}
100% {
transform: scale(2);
opacity: 0;
box-shadow:
0 -50px 0 5px currentColor,
50px 0 0 5px currentColor,
0 50px 0 5px currentColor,
-50px 0 0 5px currentColor;
}
}
/* Particle Effects */
.particle {
position: fixed;
pointer-events: none;
font-size: 1.5em;
z-index: 9999;
animation: particleBurst 0.8s ease-out forwards;
}
@keyframes particleBurst {
0% {
transform: translate(0, 0) scale(1) rotate(0deg);
opacity: 1;
}
100% {
transform: translate(var(--tx), var(--ty)) scale(0) rotate(360deg);
opacity: 0;
}
}
/* Score Update Animation */
.score-value.score-update {
animation: scorePopAnimation 0.5s ease;
}
@keyframes scorePopAnimation {
0% { transform: scale(1); }
50% { transform: scale(1.5); box-shadow: 0 0 30px currentColor; }
100% { transform: scale(1); }
}
/* Responsive Design */
@media (max-width: 768px) {
.game-layout {
flex-direction: column;
}
.score-sidebar {
width: 100%;
flex-direction: row;
justify-content: space-around;
padding: 20px;
}
.score-title {
display: none;
}
.score-item {
flex: 1;
}
.score-divider {
display: flex;
align-items: center;
}
}
@media (max-width: 600px) {
.title {
font-size: 2em;
}
.game-title {
font-size: 1.8em;
}
.cell {
font-size: 2.5em;
}
.button-group {
flex-direction: column;
}
.player-info {
flex-direction: column;
gap: 10px;
}
.vs {
display: none;
}
.container {
max-width: 100%;
}
}