// Social Share Buttons Component const SocialShareButtons = ({ score, gameStats, highScore }) => { const handleShare = (platform) => { let shareUrl; // Format time alive const totalSeconds = Math.round(gameStats.timeAlive); let timeText; if (totalSeconds < 60) { timeText = `${totalSeconds}s`; } else if (totalSeconds < 3600) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; timeText = `${minutes}m ${seconds}s`; } else { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; timeText = `${hours}h ${minutes}m ${seconds}s`; } const detailedMessage = `Game Over! Final Score: ${score} | High Score: ${highScore} | Coins Collected: ${gameStats.coinsCollected} | Power-ups Used: ${gameStats.powerUpsUsed} | Time Alive: ${timeText} | Play Swoop now!`; const encodedMessage = encodeURIComponent(detailedMessage); switch (platform) { case 'facebook': shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent('https://playswoop.com')}`; break; case 'linkedin': shareUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent('https://playswoop.com')}`; break; case 'twitter': shareUrl = `https://twitter.com/intent/tweet?text=${encodedMessage}`; break; case 'whatsapp': shareUrl = `https://wa.me/?text=${encodedMessage}`; break; default: return; } window.open(shareUrl, '_blank'); }; return (
); }; function App() { const canvasRef = React.useRef(null); const [gameState, setGameState] = React.useState('start'); // start, ready, playing, paused, gameover const [score, setScore] = React.useState(0); const [highScore, setHighScore] = React.useState(0); const [canvasSize, setCanvasSize] = React.useState({ width: 600, height: 400 }); const [copyFeedback, setCopyFeedback] = React.useState(false); const [soundEnabled, setSoundEnabled] = React.useState(true); const [playerName, setPlayerName] = React.useState(''); const [showNameInput, setShowNameInput] = React.useState(false); const [showLeaderboard, setShowLeaderboard] = React.useState(false); const [highScores, setHighScores] = React.useState(() => { try { const saved = localStorage.getItem('swoopHighScores'); return saved ? JSON.parse(saved) : []; } catch (e) { return []; } }); // Game constants const gravity = 4; const lift = -100; const baseSpeed = 120; const baseObstacleGap = 200; const baseObstacleWidth = 50; const baseObstacleFrequency = 2000; const coinFrequency = 3000; const powerUpFrequency = 8000; const coinSize = 10; const character = React.useRef({ x: 100, y: 250, vy: 0 }); const obstacles = React.useRef([]); const coins = React.useRef([]); const powerUps = React.useRef([]); const particles = React.useRef([]); const effects = React.useRef([]); const activePowerUps = React.useRef({ magnet: 0, doublePoints: 0, slowMo: 0 }); const backgroundLayers = React.useRef([]); const screenShake = React.useRef({ x: 0, y: 0, intensity: 0 }); const gameStats = React.useRef({ coinsCollected: 0, powerUpsUsed: 0, timeAlive: 0 }); const isPressing = React.useRef(false); const hasStarted = React.useRef(false); const scoreRef = React.useRef(0); const lastTime = React.useRef(performance.now()); const lastObstacleTime = React.useRef(0); const lastCoinTime = React.useRef(0); const lastPowerUpTime = React.useRef(0); // Initialize background layers React.useEffect(() => { backgroundLayers.current = []; }, []); // Handle canvas resizing React.useEffect(() => { const updateCanvasSize = () => { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; let width, height; if (viewportWidth >= 1024) { width = Math.min(800, viewportWidth * 0.8); height = Math.min(600, viewportHeight * 0.7); } else if (viewportWidth >= 768) { width = viewportWidth * 0.9; height = viewportHeight * 0.6; } else { width = viewportWidth * 0.95; height = viewportHeight * 0.5; } const aspectRatio = 4 / 3; if (width / height > aspectRatio) { width = height * aspectRatio; } else { height = width / aspectRatio; } setCanvasSize({ width: Math.round(width), height: Math.round(height) }); }; updateCanvasSize(); window.addEventListener('resize', updateCanvasSize); return () => window.removeEventListener('resize', updateCanvasSize); }, []); // Audio context and sounds const audioContext = React.useRef(null); React.useEffect(() => { if (soundEnabled && !audioContext.current) { try { audioContext.current = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { console.log('Audio not supported'); } } }, [soundEnabled]); const playSound = (frequency, duration, type = 'sine') => { if (!soundEnabled || !audioContext.current) return; const oscillator = audioContext.current.createOscillator(); const gainNode = audioContext.current.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.current.destination); oscillator.frequency.value = frequency; oscillator.type = type; gainNode.gain.setValueAtTime(0.1, audioContext.current.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.current.currentTime + duration); oscillator.start(audioContext.current.currentTime); oscillator.stop(audioContext.current.currentTime + duration); }; const saveHighScore = (newScore) => { if (newScore > highScore) { setHighScore(newScore); return true; } return false; }; const addScreenShake = (intensity) => { screenShake.current.intensity = intensity; screenShake.current.x = (Math.random() - 0.5) * intensity; screenShake.current.y = (Math.random() - 0.5) * intensity; }; const createParticles = (x, y, count, color, type = 'explosion') => { for (let i = 0; i < count; i++) { particles.current.push({ x: x, y: y, vx: (Math.random() - 0.5) * 200, vy: (Math.random() - 0.5) * 200, color: color, life: 1, size: Math.random() * 4 + 2, type: type }); } }; const createTrailParticle = (x, y) => { particles.current.push({ x: x + Math.random() * 10, y: y + Math.random() * 10, vx: -50 + Math.random() * 20, vy: (Math.random() - 0.5) * 30, color: '#4169E1', life: 0.5, size: Math.random() * 3 + 1, type: 'trail' }); }; // Event handlers React.useEffect(() => { const handleKeyDown = (e) => { if (e.code === 'Space') { e.preventDefault(); if (gameState === 'paused') { setGameState('playing'); lastTime.current = performance.now(); } else { isPressing.current = true; playSound(300, 0.1); if (gameState === 'ready' && !hasStarted.current) { hasStarted.current = true; setGameState('playing'); lastTime.current = performance.now(); lastObstacleTime.current = performance.now(); lastCoinTime.current = performance.now(); lastPowerUpTime.current = performance.now(); } } } else if (e.code === 'Escape') { e.preventDefault(); if (gameState === 'playing') { setGameState('paused'); } else if (gameState === 'paused') { setGameState('playing'); lastTime.current = performance.now(); } } else if (e.code === 'KeyR') { if (document.activeElement && document.activeElement.tagName === 'INPUT') { return; } e.preventDefault(); if (gameState === 'gameover') { if (!showNameInput) { startGame(); } } } }; const handleKeyUp = (e) => { if (e.code === 'Space') { e.preventDefault(); isPressing.current = false; } }; const handleTouchStart = (e) => { e.preventDefault(); if (e.touches.length === 2 && gameState === 'playing') { setGameState('paused'); return; } if (e.touches.length === 1 && gameState === 'paused') { setGameState('playing'); lastTime.current = performance.now(); return; } if (e.touches.length === 1) { isPressing.current = true; playSound(300, 0.1); if (gameState === 'ready' && !hasStarted.current) { hasStarted.current = true; setGameState('playing'); lastTime.current = performance.now(); lastObstacleTime.current = performance.now(); lastCoinTime.current = performance.now(); lastPowerUpTime.current = performance.now(); } } }; const handleTouchEnd = (e) => { e.preventDefault(); if (e.touches.length === 0) { isPressing.current = false; } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); const canvas = canvasRef.current; if (canvas) { canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); canvas.addEventListener('touchend', handleTouchEnd, { passive: false }); } return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); if (canvasRef.current) { canvasRef.current.removeEventListener('touchstart', handleTouchStart); canvasRef.current.removeEventListener('touchend', handleTouchEnd); } }; }, [gameState, soundEnabled, showNameInput]); // Game loop React.useEffect(() => { if (gameState !== 'ready' && gameState !== 'playing') return; let animationFrameId; const doesItemOverlap = (itemX, itemY, itemWidth, itemHeight, height) => { const margin = 30; const safeLeft = itemX - margin; const safeRight = itemX + itemWidth + margin; const safeTop = itemY - margin; const safeBottom = itemY + itemHeight + margin; for (const obs of obstacles.current) { const obsLeft = obs.x; const obsRight = obs.x + baseObstacleWidth; if (safeRight < obsLeft || safeLeft > obsRight) continue; if (safeBottom > 0 && safeTop < obs.topHeight) return true; const bottomObstacleTop = obs.topHeight + obs.gap; if (safeBottom > bottomObstacleTop && safeTop < height) return true; } return false; }; const findValidPosition = (width, height, itemWidth = coinSize * 2, itemHeight = coinSize * 2) => { const maxAttempts = 50; let attempts = 0; while (attempts < maxAttempts) { const itemY = Math.random() * (height - 150) + 75; if (!doesItemOverlap(width, itemY, itemWidth, itemHeight, height)) { return itemY; } attempts++; } return null; }; const gameLoop = () => { const currentTime = performance.now(); const baseDelta = (currentTime - lastTime.current) / 1000; const delta = activePowerUps.current.slowMo > 0 ? baseDelta * 0.5 : baseDelta; lastTime.current = currentTime; const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvasSize.width; const height = canvasSize.height; // Apply screen shake ctx.save(); ctx.translate(screenShake.current.x, screenShake.current.y); // Reduce screen shake over time screenShake.current.intensity *= 0.9; if (screenShake.current.intensity > 0.5) { screenShake.current.x = (Math.random() - 0.5) * screenShake.current.intensity; screenShake.current.y = (Math.random() - 0.5) * screenShake.current.intensity; } else { screenShake.current.x = 0; screenShake.current.y = 0; } // Draw animated background const backgroundGradient = ctx.createLinearGradient(0, 0, 0, height); backgroundGradient.addColorStop(0, '#87CEEB'); backgroundGradient.addColorStop(0.7, '#E0F6FF'); backgroundGradient.addColorStop(1, '#98FB98'); ctx.fillStyle = backgroundGradient; ctx.fillRect(0, 0, width, height); // Add moving clouds const cloudOffset = (currentTime / 2000) % (width + 100); ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; // Cloud 1 ctx.beginPath(); ctx.arc(cloudOffset - 50, height * 0.2, 20, 0, 2 * Math.PI); ctx.arc(cloudOffset - 30, height * 0.2, 25, 0, 2 * Math.PI); ctx.arc(cloudOffset - 10, height * 0.2, 20, 0, 2 * Math.PI); ctx.fill(); // Cloud 2 ctx.beginPath(); ctx.arc(cloudOffset - 200, height * 0.35, 15, 0, 2 * Math.PI); ctx.arc(cloudOffset - 185, height * 0.35, 20, 0, 2 * Math.PI); ctx.arc(cloudOffset - 170, height * 0.35, 15, 0, 2 * Math.PI); ctx.fill(); // Update power-up timers Object.keys(activePowerUps.current).forEach(key => { if (activePowerUps.current[key] > 0) { activePowerUps.current[key] -= baseDelta; } }); if (gameState === 'playing' && hasStarted.current) { gameStats.current.timeAlive += baseDelta; // Update score smoothly const scoreMultiplier = activePowerUps.current.doublePoints > 0 ? 2 : 1; scoreRef.current += delta * 50 * scoreMultiplier; // Difficulty progression const difficultyLevel = Math.floor(scoreRef.current / 500); const currentSpeed = baseSpeed + difficultyLevel * 20; const currentGap = baseObstacleGap; const currentFrequency = baseObstacleFrequency; // Apply gravity and lift if (isPressing.current) { character.current.vy += lift * delta * 0.2; } else { const dropMultiplier = character.current.vy < 0 ? 2.5 : 1; character.current.vy += gravity * delta * dropMultiplier; } character.current.y += character.current.vy; // Create trail particles if (Math.random() < 0.3) { createTrailParticle(character.current.x, character.current.y + 10); } // Boundaries if (character.current.y > height - 30 || character.current.y < 0) { createParticles(character.current.x + 10, character.current.y + 10, 20, '#FF4444'); addScreenShake(15); playSound(150, 0.5, 'sawtooth'); endGame(); return; } // Generate obstacles if (currentTime - lastObstacleTime.current > currentFrequency) { const randomGap = 80 + Math.random() * 120; const topHeight = Math.random() * (height - randomGap - 100) + 50; obstacles.current.push({ x: width, topHeight, gap: randomGap }); lastObstacleTime.current = currentTime; } // Generate coins if (currentTime - lastCoinTime.current > coinFrequency) { const validCoinY = findValidPosition(width, height); if (validCoinY !== null) { coins.current.push({ x: width, y: validCoinY }); } lastCoinTime.current = currentTime; } // Generate power-ups if (currentTime - lastPowerUpTime.current > powerUpFrequency) { const validPowerUpY = findValidPosition(width, height, 30, 30); if (validPowerUpY !== null) { const powerUpTypes = ['magnet', 'doublePoints', 'slowMo']; const powerUpType = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)]; powerUps.current.push({ x: width, y: validPowerUpY, type: powerUpType }); } lastPowerUpTime.current = currentTime; } // Move and check obstacles let collision = false; obstacles.current = obstacles.current.filter((obs) => { obs.x -= currentSpeed * delta; // Enhanced obstacle rendering with gradient const gradient = ctx.createLinearGradient(obs.x, 0, obs.x + baseObstacleWidth, 0); gradient.addColorStop(0, '#228B22'); gradient.addColorStop(1, '#006400'); ctx.fillStyle = gradient; ctx.fillRect(obs.x, 0, baseObstacleWidth, obs.topHeight); ctx.fillRect(obs.x, obs.topHeight + obs.gap, baseObstacleWidth, height - (obs.topHeight + obs.gap)); const charRight = character.current.x + 20; const charBottom = character.current.y + 20; const collidesTop = charRight > obs.x && character.current.x < obs.x + baseObstacleWidth && charBottom > 0 && character.current.y < obs.topHeight; const collidesBottom = charRight > obs.x && character.current.x < obs.x + baseObstacleWidth && charBottom > obs.topHeight + obs.gap && character.current.y < height; if (collidesTop || collidesBottom) { collision = true; } return obs.x + baseObstacleWidth > 0; }); if (collision) { createParticles(character.current.x + 10, character.current.y + 10, 20, '#FF4444'); addScreenShake(15); playSound(150, 0.5, 'sawtooth'); endGame(); return; } // Move and check coins coins.current = coins.current.filter((coin) => { coin.x -= currentSpeed * delta; // Enhanced magnet effect if (activePowerUps.current.magnet > 0) { const magnetRange = 150; const dx = character.current.x + 10 - (coin.x + coinSize); const dy = character.current.y + 10 - (coin.y + coinSize); const distance = Math.sqrt(dx * dx + dy * dy); if (distance < magnetRange) { const pullStrength = 1500; coin.x += (dx / distance) * pullStrength * delta; coin.y += (dy / distance) * pullStrength * delta; } } // Enhanced coin rendering const centerX = coin.x + coinSize; const centerY = coin.y + coinSize; const gradient = ctx.createRadialGradient(centerX - coinSize / 2, centerY - coinSize / 2, 0, centerX, centerY, coinSize); gradient.addColorStop(0, '#FFD700'); gradient.addColorStop(0.7, '#FFA500'); gradient.addColorStop(1, '#DAA520'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.arc(centerX, centerY, coinSize, 0, 2 * Math.PI); ctx.fill(); // Pulsing effect const pulse = 1 + Math.sin(currentTime / 200) * 0.1; ctx.strokeStyle = '#FFD700'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(centerX, centerY, coinSize * pulse, 0, 2 * Math.PI); ctx.stroke(); const angle = Math.sin((currentTime / 1000) * Math.PI) * (Math.PI / 2); ctx.save(); ctx.translate(centerX, centerY); ctx.rotate(angle); ctx.fillStyle = 'black'; ctx.font = 'bold 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('$', 0, 0); ctx.restore(); // Check collision with character const charRight = character.current.x + 20; const charBottom = character.current.y + 20; if ( charRight > coin.x && character.current.x < coin.x + coinSize * 2 && charBottom > coin.y && character.current.y < coin.y + coinSize * 2 ) { const coinValue = activePowerUps.current.doublePoints > 0 ? 200 : 100; scoreRef.current += coinValue; gameStats.current.coinsCollected++; effects.current.push({ x: coin.x, y: coin.y, text: `+${coinValue}`, age: 0 }); createParticles(coin.x + coinSize, coin.y + coinSize, 8, '#FFD700', 'sparkle'); playSound(800, 0.1); return false; } return coin.x + coinSize * 2 > 0; }); // Move and check power-ups powerUps.current = powerUps.current.filter((powerUp) => { powerUp.x -= currentSpeed * delta; // Enhanced power-up rendering const centerX = powerUp.x + 15; const centerY = powerUp.y + 15; let color, symbol; switch (powerUp.type) { case 'magnet': color = '#FF69B4'; symbol = '🧲'; break; case 'doublePoints': color = '#32CD32'; symbol = '2×'; break; case 'slowMo': color = '#9370DB'; symbol = '⏰'; break; } // Floating animation const float = Math.sin(currentTime / 500 + powerUp.x / 100) * 3; const adjustedY = powerUp.y + float; // Glowing effect ctx.shadowColor = color; ctx.shadowBlur = 10; ctx.fillStyle = color; ctx.fillRect(powerUp.x, adjustedY, 30, 30); ctx.shadowBlur = 0; ctx.fillStyle = 'white'; ctx.font = 'bold 16px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(symbol, centerX, adjustedY + 15); // Check collision with character const charRight = character.current.x + 20; const charBottom = character.current.y + 20; if ( charRight > powerUp.x && character.current.x < powerUp.x + 30 && charBottom > adjustedY && character.current.y < adjustedY + 30 ) { activePowerUps.current[powerUp.type] = 5; gameStats.current.powerUpsUsed++; effects.current.push({ x: powerUp.x, y: adjustedY, text: powerUp.type.toUpperCase(), age: 0 }); createParticles(powerUp.x + 15, adjustedY + 15, 10, color); addScreenShake(5); playSound(1000, 0.2); return false; } return powerUp.x + 30 > 0; }); // Update and draw effects effects.current = effects.current.filter((effect) => { effect.age += baseDelta; if (effect.age > 1.5) return false; ctx.fillStyle = `rgba(255, 215, 0, ${1 - effect.age / 1.5})`; ctx.font = 'bold 16px Arial'; ctx.strokeStyle = 'black'; ctx.lineWidth = 1; ctx.strokeText(effect.text, effect.x, effect.y - effect.age * 30); ctx.fillText(effect.text, effect.x, effect.y - effect.age * 30); return true; }); } else { character.current.vy = 0; } // Update and draw particles particles.current = particles.current.filter((particle) => { particle.life -= baseDelta * 2; if (particle.life <= 0) return false; particle.x += particle.vx * baseDelta; particle.y += particle.vy * baseDelta; particle.vy += 100 * baseDelta; ctx.globalAlpha = particle.life; if (particle.type === 'sparkle') { const twinkle = Math.sin(currentTime / 100) * 0.5 + 0.5; ctx.globalAlpha *= twinkle; } ctx.fillStyle = particle.color; ctx.beginPath(); ctx.arc(particle.x, particle.y, particle.size, 0, 2 * Math.PI); ctx.fill(); ctx.globalAlpha = 1; return true; }); // Draw character as a spaceship with enhanced visuals const charX = character.current.x; const charY = character.current.y; // Spaceship body const shipGradient = ctx.createLinearGradient(charX, charY, charX + 20, charY + 20); shipGradient.addColorStop(0, '#C0C0C0'); shipGradient.addColorStop(0.5, '#4169E1'); shipGradient.addColorStop(1, '#1E90FF'); ctx.fillStyle = shipGradient; // Main body ctx.beginPath(); ctx.ellipse(charX + 10, charY + 10, 12, 8, 0, 0, 2 * Math.PI); ctx.fill(); // Cockpit ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.ellipse(charX + 15, charY + 10, 4, 3, 0, 0, 2 * Math.PI); ctx.fill(); // Wings ctx.fillStyle = '#4169E1'; ctx.beginPath(); ctx.moveTo(charX + 5, charY + 5); ctx.lineTo(charX - 2, charY + 2); ctx.lineTo(charX - 2, charY + 18); ctx.lineTo(charX + 5, charY + 15); ctx.closePath(); ctx.fill(); // Engine glow if (isPressing.current) { const glowGradient = ctx.createRadialGradient(charX - 5, charY + 10, 0, charX - 5, charY + 10, 15); glowGradient.addColorStop(0, '#FF4500'); glowGradient.addColorStop(0.5, '#FF6347'); glowGradient.addColorStop(1, 'transparent'); ctx.fillStyle = glowGradient; ctx.beginPath(); ctx.ellipse(charX - 8, charY + 10, 8, 4, 0, 0, 2 * Math.PI); ctx.fill(); } // Draw ground with texture const groundGradient = ctx.createLinearGradient(0, height - 20, 0, height); groundGradient.addColorStop(0, '#8B4513'); groundGradient.addColorStop(1, '#654321'); ctx.fillStyle = groundGradient; ctx.fillRect(0, height - 20, width, 20); ctx.restore(); // Draw UI ctx.fillStyle = 'black'; ctx.font = 'bold 20px Arial'; ctx.strokeStyle = 'white'; ctx.lineWidth = 2; ctx.strokeText(`Score: ${Math.round(scoreRef.current)}`, 10, 30); ctx.fillText(`Score: ${Math.round(scoreRef.current)}`, 10, 30); ctx.strokeText(`High Score: ${highScore}`, 10, 55); ctx.fillText(`High Score: ${highScore}`, 10, 55); // Draw active power-ups with enhanced UI let powerUpY = 80; Object.entries(activePowerUps.current).forEach(([key, time]) => { if (time > 0) { const barWidth = 100; const barHeight = 6; const progress = time / 5; ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(10, powerUpY - 15, barWidth, barHeight); ctx.fillStyle = time < 1 ? '#FF4444' : '#44FF44'; ctx.fillRect(10, powerUpY - 15, barWidth * progress, barHeight); ctx.fillStyle = time < 1 ? '#FF4444' : '#44FF44'; ctx.font = '12px Arial'; ctx.fillText(`${key.toUpperCase()}: ${time.toFixed(1)}s`, 10, powerUpY); powerUpY += 25; } }); animationFrameId = requestAnimationFrame(gameLoop); }; animationFrameId = requestAnimationFrame(gameLoop); return () => { cancelAnimationFrame(animationFrameId); }; }, [gameState, highScore]); const startGame = () => { setGameState('ready'); setScore(0); scoreRef.current = 0; character.current = { x: 100, y: canvasSize.height / 2, vy: 0 }; obstacles.current = []; coins.current = []; powerUps.current = []; particles.current = []; effects.current = []; activePowerUps.current = { magnet: 0, doublePoints: 0, slowMo: 0 }; gameStats.current = { coinsCollected: 0, powerUpsUsed: 0, timeAlive: 0 }; isPressing.current = false; hasStarted.current = false; lastTime.current = performance.now(); lastObstacleTime.current = performance.now(); lastCoinTime.current = performance.now(); lastPowerUpTime.current = performance.now(); }; const endGame = () => { const finalScore = Math.round(scoreRef.current); setScore(finalScore); const isNewHighScore = saveHighScore(finalScore); setGameState('gameover'); setShowNameInput(true); if (isNewHighScore) { effects.current.push({ x: 300, y: 200, text: 'NEW HIGH SCORE!', age: 0 }); playSound(1200, 0.5); } }; const saveScoreWithName = () => { if (playerName.trim()) { const newScore = { name: playerName.trim(), score: score, coinsCollected: gameStats.current.coinsCollected, powerUpsUsed: gameStats.current.powerUpsUsed, timeAlive: gameStats.current.timeAlive, date: new Date().toLocaleDateString() }; const updatedScores = [...highScores, newScore].sort((a, b) => b.score - a.score); setHighScores(updatedScores); try { localStorage.setItem('swoopHighScores', JSON.stringify(updatedScores)); } catch (e) { console.log('Failed to save high scores to localStorage'); } setShowNameInput(false); setPlayerName(''); } }; const toggleLeaderboard = () => { setShowLeaderboard(!showLeaderboard); }; const copyScoreToClipboard = async () => { const totalSeconds = Math.round(gameStats.current.timeAlive); let timeText; if (totalSeconds < 60) { timeText = `${totalSeconds}s`; } else if (totalSeconds < 3600) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; timeText = `${minutes}m ${seconds}s`; } else { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; timeText = `${hours}h ${minutes}m ${seconds}s`; } const message = `Game Over! Final Score: ${score} | High Score: ${highScore} | Coins Collected: ${gameStats.current.coinsCollected} | Power-ups Used: ${gameStats.current.powerUpsUsed} | Time Alive: ${timeText} | Play Swoop now!`; try { await navigator.clipboard.writeText(message); setCopyFeedback(true); setTimeout(() => setCopyFeedback(false), 2000); } catch (err) { const textArea = document.createElement('textarea'); textArea.value = message; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); setCopyFeedback(true); setTimeout(() => setCopyFeedback(false), 2000); } }; return (

🚀 Play Swoop 🚀

{gameState === 'gameover' && (
)} {gameState === 'paused' && (
)}
{gameState === 'start' && (

💫 Power-ups:

🧲 Magnet - Attracts nearby coins
2× Double Points - 2x score for coins
⏰ Slow Mo - Slows down time
)} {gameState === 'ready' && (

🚀 Press Spacebar or Touch Screen to begin flying!

)} {gameState === 'paused' && (

⏸️ Game Paused - Press Spacebar to Resume

)} {gameState === 'gameover' && (
{showNameInput && (

🏆 Save Your Score!

setPlayerName(e.target.value)} className="px-3 py-2 border-2 border-blue-300 rounded-lg text-center font-medium" maxLength="20" />
)} {showLeaderboard && (

🏆 High Scores

{highScores.length === 0 ? (

No scores saved yet!

) : (
{highScores.map((entry, index) => (
#{index + 1} {entry.name}
{entry.score} pts
{entry.date}
))}
)}
)}

📤 Share Your Score

💥 Game Over!

Final Score: {score}

{copyFeedback && (
✅ Copied!
)}

High Score: {highScore}

Coins Collected: {gameStats.current.coinsCollected}

Power-ups Used: {gameStats.current.powerUpsUsed}

Time Alive: {(() => { const totalSeconds = Math.round(gameStats.current.timeAlive); if (totalSeconds < 60) { return `${totalSeconds}s`; } else if (totalSeconds < 3600) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}m ${seconds}s`; } else { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return `${hours}h ${minutes}m ${seconds}s`; } })()}

{showNameInput ? 'Save your score or skip to enable R key restart' : 'Press R to restart'}

)}

Hold Spacebar or Touch Screen to fly up. Release to descend. Press Esc or two-finger touch to pause. Avoid green obstacles, collect gold coins and power-ups! 🎯

); }