coconut / index.html
kimhyunwoo's picture
Update index.html
c701a1d verified
<!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>