commit 4b7d42dc13f9208ad58cf00bced51e3d3233cc16 Author: Parley Hatch Date: Sun May 31 17:43:45 2026 -0600 Initial commit: original tic-tac-toe game Existing 2-player hotseat game with name entry, emoji avatars, scoreboard, sound effects, and celebration animations. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91c2279 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# OS / editor cruft +.DS_Store +Thumbs.db +*.swp +.idea/ +.vscode/ + +# Test / tooling artifacts +/screenshots/ +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d0c5dc --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Kara's ultimate tic tac toe + +## Objective + +Design an ultimate browser based tic tac toe game with the following features: + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..86132ef --- /dev/null +++ b/index.html @@ -0,0 +1,153 @@ + + + + + + ✨ Ultimate Tic-Tac-Toe ✨ + + + +
+
+ +
+
+

✨ Ultimate Tic-Tac-Toe ✨

+
+ +
+ + +
+ +
+ + +
+ +
+

Choose Your Avatars!

+
+
+ +
+
+
🦄
+
🌸
+
🎀
+
🦋
+
🌈
+
🎨
+
🎪
+
+
+
+ +
+
+
🦄
+
🌸
+
🎀
+
🦋
+
🌈
+
🎨
+
🎪
+
+
+
+
+ + +
+
+ + +
+
+

✨ Tic-Tac-Toe ✨

+ +
+ +
+
+
+ X + +
+
+
VS
+
+ O + +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+
+ + +
+

Scoreboard

+
+ + Player 1 + 0 +
+
🏆
+
+ 🌸 + Player 2 + 0 +
+
+
+
+
+ + +
+
+
+

🎉 Winner! 🎉

+
🏆
+

+
+
+
+
+
+ + +
+
+
+ + + + diff --git a/script.js b/script.js new file mode 100644 index 0000000..e8e4809 --- /dev/null +++ b/script.js @@ -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(); diff --git a/style.css b/style.css new file mode 100644 index 0000000..65505d9 --- /dev/null +++ b/style.css @@ -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%; + } +}