diff --git a/game.js b/game.js index 9f08e3c..4e91622 100644 --- a/game.js +++ b/game.js @@ -359,12 +359,15 @@ function handleLevelCompletion(squares) { const finalBonus = baseTimeBonus + speedBonus + comboBonus; - // Show bonus breakdown - showTimeBonusPopup(finalBonus, { - base: baseTimeBonus, - speed: speedBonus, - combo: comboBonus - }); + // Show bonus notification + let bonusMessage = `Level Complete! +${finalBonus}s`; + if (speedBonus > 0) { + bonusMessage += `
Speed Bonus: +${speedBonus}s`; + } + if (comboBonus > 0) { + bonusMessage += `
Combo Bonus: +${comboBonus}s`; + } + showNotification(bonusMessage, 2000); // Clear squares with effects squares.forEach(square => { @@ -408,25 +411,42 @@ function handleLevelCompletion(squares) { function handleSquareClick(square, squares) { const index = squares.indexOf(square); if (index > -1) { + let scoreChange = 0; + let livesChange = 0; + let bonusChange = 0; + let bonusLife = false; + let notificationParts = []; + // Handle scoring if (square.color === gameConfig.colors.good) { - gameState.score += 1; + scoreChange = 1; gameState.currentCombo++; - showPointGainPopup(1); + notificationParts.push(`+${scoreChange} Point${scoreChange !== 1 ? 's' : ''}`); } else if (square.color === gameConfig.colors.bonus) { - gameState.score += 3; + scoreChange = 2; + bonusChange = 2; gameState.currentCombo += 2; - showPointGainPopup(3); + notificationParts.push(`+${scoreChange} Bonus Point${scoreChange !== 1 ? 's' : ''}`); + // Bonus life logic (medium only, if lost a life) + const config = gameConfig.difficulties[gameState.selectedDifficulty]; + if ( + gameState.selectedDifficulty === 'medium' && + gameState.lives < config.lives && + Math.random() < 0.1 // 10% chance + ) { + gameState.lives += 1; + bonusLife = true; + notificationParts.push('+1 Bonus Life'); + } } // Handle red squares if (square.color === gameConfig.colors.bad) { const config = gameConfig.difficulties[gameState.selectedDifficulty]; if (config.lives !== Infinity) { - gameState.lives--; + livesChange = -1; gameState.currentCombo = 0; - showLifeLossPopup(1); - + notificationParts.push('-1 Life'); if (gameState.lives <= 0) { gameState.isPlaying = false; showDeathScreen(); @@ -434,6 +454,15 @@ function handleSquareClick(square, squares) { } } + // Update game state + gameState.score += scoreChange; + gameState.lives += livesChange; + + // Create notification message + if (notificationParts.length > 0) { + showNotification(notificationParts.join(', ')); + } + // Create effects for (let i = 0; i < gameConfig.particleCount; i++) { const angle = (Math.PI * 2 * i) / gameConfig.particleCount; @@ -531,8 +560,106 @@ function initializeGame() { squareCenterY <= bottom; }); - // Handle selected squares - selectedSquares.forEach(square => handleSquareClick(square, squares)); + // Calculate total changes + let totalScoreChange = 0; + let totalBonusChange = 0; + let totalLivesChange = 0; + let bonusLife = false; + let notificationParts = []; + + // Handle each selected square + selectedSquares.forEach(square => { + if (square.color === gameConfig.colors.good) { + totalScoreChange += 1; + gameState.currentCombo++; + } else if (square.color === gameConfig.colors.bonus) { + totalScoreChange += 2; + totalBonusChange += 2; + gameState.currentCombo += 2; + + // Bonus life logic (medium only, if lost a life) + const config = gameConfig.difficulties[gameState.selectedDifficulty]; + if ( + gameState.selectedDifficulty === 'medium' && + gameState.lives < config.lives && + Math.random() < 0.1 // 10% chance + ) { + totalLivesChange += 1; + bonusLife = true; + } + } else if (square.color === gameConfig.colors.bad) { + const config = gameConfig.difficulties[gameState.selectedDifficulty]; + if (config.lives !== Infinity) { + totalLivesChange -= 1; + gameState.currentCombo = 0; + } + } + + // Create effects + for (let i = 0; i < gameConfig.particleCount; i++) { + const angle = (Math.PI * 2 * i) / gameConfig.particleCount; + const x = square.x + square.size / 2; + const y = square.y + square.size / 2; + createParticle(x, y, square.color); + } + + // Extra sparkles for bonus squares + if (square.color === gameConfig.colors.bonus) { + for (let i = 0; i < gameConfig.sparkleCount; i++) { + const angle = (Math.PI * 2 * i) / gameConfig.sparkleCount; + const x = square.x + square.size / 2 + Math.cos(angle) * 20; + const y = square.y + square.size / 2 + Math.sin(angle) * 20; + createSparkle(x, y); + } + } + }); + + // Build notification message + if (totalScoreChange > 0) { + notificationParts.push(`+${totalScoreChange} Point${totalScoreChange !== 1 ? 's' : ''}`); + } + if (totalBonusChange > 0) { + notificationParts.push(`+${totalBonusChange} Bonus Point${totalBonusChange !== 1 ? 's' : ''}`); + } + if (totalLivesChange > 0) { + notificationParts.push(`+${totalLivesChange} Bonus Life${totalLivesChange !== 1 ? 's' : ''}`); + } + if (totalLivesChange < 0) { + notificationParts.push(`${totalLivesChange} Life${totalLivesChange !== -1 ? 's' : ''}`); + } + + // Show notification if there are any changes + if (notificationParts.length > 0) { + showNotification(notificationParts.join(', ')); + } + + // Update game state + gameState.score += totalScoreChange; + gameState.lives += totalLivesChange; + + // Check for game over + if (gameState.lives <= 0) { + gameState.isPlaying = false; + showDeathScreen(); + } + + // Remove selected squares + selectedSquares.forEach(square => { + const index = squares.indexOf(square); + if (index > -1) { + squares.splice(index, 1); + } + }); + + // Update max combo + if (gameState.currentCombo > gameState.maxCombo) { + gameState.maxCombo = gameState.currentCombo; + } + + // Check for level completion + if (isLevelComplete(squares)) { + handleLevelCompletion(squares); + } updateStats(); } @@ -912,4 +1039,15 @@ function showTimeBonusPopup(seconds, breakdown) { timeBonusPopup.classList.remove('show'); timeBonusPopup.remove(); }, 2000); +} + +// New notification system +function showNotification(message, duration = 1500) { + const notificationContent = document.querySelector('.notification-content'); + notificationContent.innerHTML = message; + notificationContent.classList.add('show'); + + setTimeout(() => { + notificationContent.classList.remove('show'); + }, duration); } \ No newline at end of file diff --git a/index.html b/index.html index 4af7ade..4cd6113 100644 --- a/index.html +++ b/index.html @@ -51,6 +51,9 @@

Zone Out

+
+
+
diff --git a/styles.css b/styles.css index 675b9f1..3575c69 100644 --- a/styles.css +++ b/styles.css @@ -642,4 +642,49 @@ button:hover { transform: scale(1); opacity: 1; } +} + +.notification-banner { + position: fixed; + top: 120px; + left: 50%; + transform: translateX(-50%); + z-index: 1000; + width: 300px; + pointer-events: none; +} + +.notification-content { + background: rgba(15, 23, 42, 0.95); + backdrop-filter: blur(8px); + border: 1px solid rgba(96, 165, 250, 0.2); + border-radius: 12px; + padding: 12px 20px; + color: white; + font-size: 1.1rem; + text-align: center; + opacity: 0; + transform: translateY(-20px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.notification-content.show { + opacity: 1; + transform: translateY(0); +} + +.notification-content .score-change { + color: #4ecca3; + font-weight: 600; +} + +.notification-content .lives-change { + color: #ff6b6b; + font-weight: 600; +} + +.notification-content .bonus-change { + color: #ffd93d; + font-weight: 600; } \ No newline at end of file