<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <title>Mango Coconut Rhythm Game</title> <style> body { font-family: 'Arial', sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; background: linear-gradient(135deg, #FFD700, #FFA500); /* Mango/Orange gradient */ color: #fff; text-align: center; overflow: hidden; touch-action: manipulation; /* Prevents zoom on double tap, better for games */ } #game-container { background-color: rgba(255, 255, 255, 0.1); padding: 30px; border-radius: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); width: 90%; max-width: 400px; } #target-fruit { font-size: 3em; font-weight: bold; margin-bottom: 20px; color: #FFF; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); height: 60px; /* Fixed height to prevent layout shifts */ } #input-area { margin-bottom: 20px; display: flex; justify-content: center; align-items: center; flex-wrap: wrap; /* Allow dots to wrap */ } .tap-indicator { width: 20px; height: 20px; background-color: rgba(255,255,255,0.3); border-radius: 50%; margin: 0 5px; transition: background-color 0.1s ease; } .tap-indicator.active { background-color: #FFEB3B; /* Bright yellow for active tap */ } #tap-button { padding: 20px 40px; font-size: 1.5em; background-color: #FFC107; /* Mango yellow */ color: #8C5B00; /* Darker text for contrast */ border: none; border-radius: 10px; cursor: pointer; box-shadow: 0 5px 15px rgba(0,0,0,0.15); transition: background-color 0.2s, transform 0.1s; user-select: none; /* Prevent text selection on rapid clicks */ -webkit-tap-highlight-color: transparent; /* Remove tap highlight on mobile */ } #tap-button:active { background-color: #FFA000; /* Darker when pressed */ transform: scale(0.95); } #feedback { font-size: 1.5em; margin-top: 20px; height: 30px; /* Fixed height */ font-weight: bold; } .feedback-correct { color: #A5D6A7; /* Light green */ } .feedback-incorrect { color: #EF9A9A; /* Light red */ } #score-display { margin-top: 15px; font-size: 1.2em; } #start-button { padding: 15px 30px; font-size: 1.2em; background-color: #4CAF50; /* Green */ color: white; border: none; border-radius: 8px; cursor: pointer; margin-top: 20px; } #instructions { margin-top: 20px; font-size: 0.9em; color: rgba(255,255,255,0.8); } </style> </head> <body> <div id="game-container"> <div id="target-fruit">Press Start!</div> <div id="input-area"> <!-- Tap indicators will be generated here --> </div> <button id="tap-button" disabled>TAP</button> <div id="feedback"></div> <div id="score-display">Score: 0</div> <button id="start-button">Start Game</button> <div id="instructions"> <p>Tap the rhythm for the displayed fruit!<br> MANGO: 2 taps (tap-tap)<br> COCONUT: 3 taps (tap-tap-tap)<br> Press SPACE or TAP button.</p> </div> </div> <script> const targetFruitDisplay = document.getElementById('target-fruit'); const inputArea = document.getElementById('input-area'); const tapButton = document.getElementById('tap-button'); const feedbackDisplay = document.getElementById('feedback'); const scoreDisplay = document.getElementById('score-display'); const startButton = document.getElementById('start-button'); const fruits = [ { name: 'MANGO', rhythm: [1, 1], beats: 2 }, // 2 taps { name: 'COCONUT', rhythm: [1, 1, 1], beats: 3 } // 3 taps ]; let currentFruit = null; let currentTapCount = 0; let expectedTaps = 0; let score = 0; let gameActive = false; let inputTimeout; // Web Audio API setup let audioContext; const tapFrequency = 440; // A4 note for tap const successFrequency = 660; // E5 note for success const failFrequency = 220; // A3 note for fail function initAudioContext() { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)(); } } function playTone(frequency, duration = 0.1, type = 'sine') { if (!audioContext) return; const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.type = type; oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); gainNode.gain.setValueAtTime(0.5, audioContext.currentTime); // Volume gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.start(); oscillator.stop(audioContext.currentTime + duration); } function setupNextFruit() { currentTapCount = 0; currentFruit = fruits[Math.floor(Math.random() * fruits.length)]; expectedTaps = currentFruit.beats; targetFruitDisplay.textContent = currentFruit.name; feedbackDisplay.textContent = ''; feedbackDisplay.className = ''; // Clear feedback color renderTapIndicators(); clearTimeout(inputTimeout); // Clear previous timeout // Start a timeout for the current fruit - player must complete within X seconds inputTimeout = setTimeout(() => { if (gameActive && currentTapCount < expectedTaps && currentTapCount > 0) { // Partial input handleResult(false, "Too slow!"); } else if (gameActive && currentTapCount === 0) { // No input // Optional: penalty or just move on // console.log("No input for", currentFruit.name); // setupNextFruit(); // Silently move to next } }, 3000 + expectedTaps * 500); // Generous timeout based on beats } function renderTapIndicators() { inputArea.innerHTML = ''; for (let i = 0; i < expectedTaps; i++) { const indicator = document.createElement('div'); indicator.classList.add('tap-indicator'); if (i < currentTapCount) { indicator.classList.add('active'); } inputArea.appendChild(indicator); } } function handleTap() { if (!gameActive || !currentFruit) return; initAudioContext(); // Ensure audio context is started on user interaction playTone(tapFrequency, 0.05); // Short tap sound currentTapCount++; renderTapIndicators(); clearTimeout(inputTimeout); // Reset timeout on each tap if (currentTapCount === expectedTaps) { handleResult(true); } else if (currentTapCount > expectedTaps) { // This case should ideally not be reached if logic is tight handleResult(false, "Too many taps!"); } else { // Set a short timeout for the next tap in the sequence inputTimeout = setTimeout(() => { if (gameActive && currentTapCount < expectedTaps) { handleResult(false, "Rhythm incomplete!"); } }, 800); // Time window for next tap in sequence } } function handleResult(isCorrect, message = "") { if (!gameActive) return; if (isCorrect) { feedbackDisplay.textContent = 'GREAT!'; feedbackDisplay.className = 'feedback-correct'; playTone(successFrequency, 0.2, 'triangle'); score++; scoreDisplay.textContent = `Score: ${score}`; setTimeout(setupNextFruit, 700); // Wait a bit then show next fruit } else { feedbackDisplay.textContent = message || 'MISSED!'; feedbackDisplay.className = 'feedback-incorrect'; playTone(failFrequency, 0.3, 'sawtooth'); // Optional: Implement lives or game over condition // For now, just move to the next fruit after a delay setTimeout(setupNextFruit, 1200); } currentTapCount = 0; // Reset for safety, though setupNextFruit also does this } function startGame() { initAudioContext(); gameActive = true; score = 0; scoreDisplay.textContent = `Score: ${score}`; tapButton.disabled = false; startButton.style.display = 'none'; // Hide start button document.getElementById('instructions').style.display = 'none'; setupNextFruit(); } startButton.addEventListener('click', startGame); tapButton.addEventListener('click', handleTap); // Keyboard support (Spacebar) document.addEventListener('keydown', (event) => { if (event.code === 'Space') { if (!gameActive && startButton.style.display !== 'none') { startGame(); } else if (gameActive) { tapButton.click(); // Simulate button click for visual feedback and logic } event.preventDefault(); // Prevent page scroll } }); </script> </body> </html>