import React, { useState, useEffect, useRef, useCallback } from 'react';
// === KONFIGURASI GAME ===
const TARGET_SCORE = 500;
const GAME_1_DURATION = 50;
const ICONS = [
{ type: 'healthy', emoji: '🍎', points: 10 },
{ type: 'healthy', emoji: '🍊', points: 10 },
{ type: 'healthy', emoji: '🍋', points: 10 },
{ type: 'healthy', emoji: '🍉', points: 10 },
{ type: 'healthy', emoji: '🍯', points: 10 },
{ type: 'unhealthy', emoji: '🍔', points: -10 },
{ type: 'unhealthy', emoji: '🍟', points: -10 },
{ type: 'unhealthy', emoji: '🥤', points: -10 },
];
// --- KOMPONEN: LAYAR UTAMA ---
const Home = ({ playerName, setPlayerName, setScore, setTimeDiff, setScreen, initAudio, audioEnabled, setAudioEnabled }) => (
🥤🍋
Fresh & Healthy Stand
Mainkan game singkat ini sebelum memesan dan dapatkan kesempatan memenangkan Minuman GRATIS atau diskon menarik!
setPlayerName(e.target.value)}
/>
);
// --- KOMPONEN: GAME 1 (HEALTHY CATCH) ---
const Game1 = ({ score, setScore, setScreen, setReward, saveToLeaderboard, playSound }) => {
const [timeLeft, setTimeLeft] = useState(GAME_1_DURATION);
const [items, setItems] = useState([]);
// Timer Mundur
useEffect(() => {
if (timeLeft > 0) {
const timer = setInterval(() => setTimeLeft(t => t - 1), 1000);
return () => clearInterval(timer);
}
}, [timeLeft]);
// Cek kalau skor langsung tercapai (langsung lanjut ke 10 detik tanpa nunggu waktu abis)
useEffect(() => {
if (score >= TARGET_SCORE) {
playSound('win');
setScreen('GAME2');
}
}, [score, playSound, setScreen]);
// Cek kalau waktu abis tapi skor kurang (TETAP LANJUT KE GAME 2)
useEffect(() => {
if (timeLeft <= 0 && score < TARGET_SCORE) {
playSound('lose'); // Cuma kasih suara "womp womp" penanda waktu habis
setScreen('GAME2'); // Tapi TETAP lanjut ke game 10 detik
}
}, [timeLeft, score, playSound, setScreen]);
// Game Loop: Munculin Buah
useEffect(() => {
const spawner = setInterval(() => {
const randomIcon = ICONS[Math.floor(Math.random() * ICONS.length)];
const newItem = {
id: Math.random().toString(),
...randomIcon,
x: Math.floor(Math.random() * 80) + 10,
y: -10,
speed: Math.random() * 2 + 1.5 // Kecepatan variatif
};
setItems(prev => [...prev, newItem]);
}, 350); // Buah dibikin lebih sering turun biar gampang capai target 500
const mover = setInterval(() => {
setItems(prev => prev.map(item => ({...item, y: item.y + item.speed})).filter(item => item.y < 110));
}, 50);
return () => {
clearInterval(spawner);
clearInterval(mover);
};
}, []);
const catchItem = (id, type, points) => {
if (type === 'healthy') {
playSound('catchGood');
} else {
playSound('catchBad');
}
setScore(s => s + points);
setItems(prev => prev.filter(item => item.id !== id));
};
return (
TARGET: {TARGET_SCORE}
= TARGET_SCORE ? 'text-green-600' : 'text-gray-700'}`}>Skor: {score}
Klik yang sehat!
{items.map(item => (
catchItem(item.id, item.type, item.points)}
className="absolute text-5xl hover:scale-110 transition-transform select-none drop-shadow-md"
style={{ left: `${item.x}%`, top: `${item.y}%`, cursor: 'pointer' }}
>
{item.emoji}
))}
);
};
// --- KOMPONEN: GAME 2 (10-SECOND CHALLENGE) ---
const Game2 = ({ setScreen, timeDiff, setTimeDiff, setReward, saveToLeaderboard, playSound }) => {
const [gameState, setGameState] = useState('IDLE');
const [displayTime, setDisplayTime] = useState('0.00');
const startTimeRef = useRef(null);
const animationRef = useRef(null);
const startTimer = () => {
setGameState('RUNNING');
playSound('catchGood');
startTimeRef.current = performance.now();
const updateTimer = () => {
const now = performance.now();
const elapsed = (now - startTimeRef.current) / 1000;
if (elapsed > 3.0) {
setDisplayTime('??.??');
} else {
setDisplayTime(elapsed.toFixed(2));
}
animationRef.current = requestAnimationFrame(updateTimer);
};
animationRef.current = requestAnimationFrame(updateTimer);
};
const stopTimer = () => {
cancelAnimationFrame(animationRef.current);
const now = performance.now();
const elapsed = (now - startTimeRef.current) / 1000;
const diff = Math.abs(10 - elapsed);
setDisplayTime(elapsed.toFixed(2));
setGameState('DONE');
setTimeDiff(diff);
let finalReward = '';
if (diff === 0) {
finalReward = 'GRAND PRIZE: GRATIS 1 CUP!';
playSound('win');
}
else if (diff <= 0.15) {
finalReward = 'DISKON 50%';
playSound('win');
}
else if (diff <= 0.50) {
finalReward = 'GRATIS EXTRA TOPPING';
playSound('win');
}
else {
finalReward = 'Harga Normal. Semangat ya!';
playSound('lose');
}
setReward(finalReward);
saveToLeaderboard(diff, finalReward);
setTimeout(() => {
setScreen('RESULT');
}, 2500);
};
return (
Bonus Round!
Hentikan timer tepat di angka 10.00 detik.
Timer akan menghilang di detik ke-3.
{displayTime}
{gameState === 'IDLE' && (
)}
{gameState === 'RUNNING' && (
)}
{gameState === 'DONE' && (
{timeDiff <= 0.5 ? '🎉 Wah Hampir Tepat! 🎉' : 'Aduh Meleset...'}
)}
);
};
// --- KOMPONEN: LAYAR HASIL ---
const ResultScreen = ({ playerName, setPlayerName, score, timeDiff, reward, setScreen }) => (
{timeDiff !== null && timeDiff <= 0.5 && (
{[...Array(20)].map((_, i) => (
))}
)}
TANTANGAN SELESAI!
Pemain: {playerName}
Skor Nangkep Buah
{score} Poin
{timeDiff !== null && (
Selisih Waktu Fokus
± {timeDiff.toFixed(2)} detik
)}
);
// --- KOMPONEN: LEADERBOARD ---
const LeaderboardScreen = ({ leaderboard, setLeaderboard, setScreen }) => (
🏆 WALL OF FAME 🏆
Pemain dengan fokus terbaik hari ini!
| Rank |
Nama |
Selisih Waktu |
{leaderboard.length === 0 ? (
|
Belum ada yang main. Jadilah yang pertama!
|
) : (
leaderboard.map((entry, index) => (
|
{index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : index + 1}
|
{entry.name}
{entry.reward}
|
{entry.diff !== null ? `±${entry.diff.toFixed(2)}s` : 'Gagal Babak 1'}
|
))
)}
);
export default function App() {
const [screen, setScreen] = useState('HOME');
const [playerName, setPlayerName] = useState('');
const [score, setScore] = useState(0);
const [timeDiff, setTimeDiff] = useState(null);
const [reward, setReward] = useState('');
const [leaderboard, setLeaderboard] = useState([]);
const [audioEnabled, setAudioEnabled] = useState(true);
// Load leaderboard
useEffect(() => {
const saved = localStorage.getItem('standLeaderboard');
if (saved) setLeaderboard(JSON.parse(saved));
}, []);
const saveToLeaderboard = useCallback((finalDiff, finalReward) => {
const newEntry = {
name: playerName,
score: score,
diff: finalDiff,
reward: finalReward,
date: new Date().toLocaleTimeString()
};
const newLeaderboard = [...leaderboard, newEntry].sort((a, b) => {
if (a.diff === null) return 1;
if (b.diff === null) return -1;
return a.diff - b.diff;
}).slice(0, 5);
setLeaderboard(newLeaderboard);
localStorage.setItem('standLeaderboard', JSON.stringify(newLeaderboard));
}, [leaderboard, playerName, score]);
const audioCtxRef = useRef(null);
const initAudio = () => {
if (!audioCtxRef.current) {
audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtxRef.current.state === 'suspended') {
audioCtxRef.current.resume();
}
};
const playSound = useCallback((type) => {
if (!audioEnabled || !audioCtxRef.current) return;
const ctx = audioCtxRef.current;
const osc = ctx.createOscillator();
const gainNode = ctx.createGain();
osc.connect(gainNode);
gainNode.connect(ctx.destination);
const now = ctx.currentTime;
if (type === 'catchGood') {
osc.type = 'sine';
osc.frequency.setValueAtTime(800, now);
osc.frequency.exponentialRampToValueAtTime(1200, now + 0.1);
gainNode.gain.setValueAtTime(0.3, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.1);
osc.start(now);
osc.stop(now + 0.1);
} else if (type === 'catchBad') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(150, now);
gainNode.gain.setValueAtTime(0.3, now);
gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.2);
osc.start(now);
osc.stop(now + 0.2);
} else if (type === 'win') {
osc.type = 'square';
osc.frequency.setValueAtTime(400, now);
osc.frequency.setValueAtTime(500, now + 0.1);
osc.frequency.setValueAtTime(600, now + 0.2);
osc.frequency.setValueAtTime(800, now + 0.3);
gainNode.gain.setValueAtTime(0.3, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.5);
osc.start(now);
osc.stop(now + 0.5);
} else if (type === 'lose') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(300, now);
osc.frequency.exponentialRampToValueAtTime(100, now + 0.5);
gainNode.gain.setValueAtTime(0.5, now);
gainNode.gain.linearRampToValueAtTime(0, now + 0.5);
osc.start(now);
osc.stop(now + 0.5);
}
}, [audioEnabled]);
return (
{screen === 'HOME' && }
{screen === 'GAME1' && }
{screen === 'GAME2' && }
{screen === 'RESULT' && }
{screen === 'LEADERBOARD' && }
);
}