| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> |
| <title>Squirrel Rush</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://unpkg.com/lucide@latest"></script> |
| <style> |
| @import url('https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;700&display=swap'); |
| |
| body { |
| font-family: 'Nunito', sans-serif; |
| touch-action: none; |
| overflow: hidden; |
| } |
| |
| .game-font { |
| font-family: 'Fredoka One', cursive; |
| } |
| |
| canvas { |
| image-rendering: pixelated; |
| } |
| |
| .bounce { |
| animation: bounce 2s infinite; |
| } |
| |
| @keyframes bounce { |
| 0%, 100% { transform: translateY(0); } |
| 50% { transform: translateY(-10px); } |
| } |
| |
| .pulse-glow { |
| animation: pulse-glow 2s infinite; |
| } |
| |
| @keyframes pulse-glow { |
| 0%, 100% { box-shadow: 0 0 20px rgba(251, 191, 36, 0.5); } |
| 50% { box-shadow: 0 0 40px rgba(251, 191, 36, 0.8); } |
| } |
| |
| .shake { |
| animation: shake 0.5s; |
| } |
| |
| @keyframes shake { |
| 0%, 100% { transform: translateX(0); } |
| 25% { transform: translateX(-5px); } |
| 75% { transform: translateX(5px); } |
| } |
| </style> |
| </head> |
| <body class="bg-gradient-to-b from-sky-300 to-sky-100 h-screen w-screen overflow-hidden relative select-none"> |
|
|
| |
| <canvas id="gameCanvas" class="absolute inset-0 w-full h-full block"></canvas> |
|
|
| |
| <div id="uiLayer" class="absolute inset-0 pointer-events-none flex flex-col justify-between p-4"> |
| |
| |
| <div class="flex justify-between items-start"> |
| <div class="bg-white/90 backdrop-blur-sm rounded-2xl px-4 py-2 shadow-lg border-2 border-amber-400 flex items-center gap-2"> |
| <i data-lucide="acorn" class="w-6 h-6 text-amber-700 fill-amber-600"></i> |
| <span id="scoreDisplay" class="game-font text-2xl text-amber-900">0</span> |
| </div> |
| |
| <div class="bg-white/90 backdrop-blur-sm rounded-2xl px-4 py-2 shadow-lg border-2 border-emerald-400 flex items-center gap-2"> |
| <i data-lucide="trophy" class="w-5 h-5 text-emerald-600"></i> |
| <span id="highScoreDisplay" class="game-font text-xl text-emerald-900">0</span> |
| </div> |
| </div> |
|
|
| |
| <div id="mobileHint" class="hidden absolute bottom-20 left-1/2 -translate-x-1/2 text-white/80 text-sm font-bold animate-pulse"> |
| TAP TO JUMP |
| </div> |
| </div> |
|
|
| |
| <div id="startScreen" class="absolute inset-0 flex items-center justify-center bg-black/40 backdrop-blur-sm z-50"> |
| <div class="bg-white rounded-3xl p-8 max-w-md w-full mx-4 shadow-2xl border-4 border-amber-500 text-center"> |
| <div class="mb-6"> |
| <div class="w-24 h-24 bg-gradient-to-br from-amber-400 to-orange-500 rounded-full mx-auto flex items-center justify-center shadow-lg bounce"> |
| <span class="text-5xl">🐿️</span> |
| </div> |
| </div> |
| <h1 class="game-font text-4xl text-amber-700 mb-2">Squirrel Rush</h1> |
| <p class="text-gray-600 mb-6 text-lg">Jump over logs, collect acorns, and run as far as you can!</p> |
| |
| <div class="space-y-3 mb-6"> |
| <div class="flex items-center justify-center gap-2 text-sm text-gray-500"> |
| <span class="bg-gray-100 px-3 py-1 rounded-lg border">SPACE</span> |
| <span>or</span> |
| <span class="bg-gray-100 px-3 py-1 rounded-lg border">↑</span> |
| <span>to Jump</span> |
| </div> |
| </div> |
|
|
| <button id="startBtn" class="w-full bg-gradient-to-r from-amber-500 to-orange-500 hover:from-amber-600 hover:to-orange-600 text-white game-font text-xl py-4 rounded-2xl shadow-lg transform transition hover:scale-105 active:scale-95 pulse-glow pointer-events-auto"> |
| PLAY NOW |
| </button> |
| </div> |
| </div> |
|
|
| |
| <div id="gameOverScreen" class="hidden absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm z-50"> |
| <div class="bg-white rounded-3xl p-8 max-w-md w-full mx-4 shadow-2xl border-4 border-red-400 text-center"> |
| <div class="mb-4"> |
| <span class="text-6xl">🍂</span> |
| </div> |
| <h2 class="game-font text-3xl text-gray-800 mb-2">Game Over!</h2> |
| <p class="text-gray-600 mb-4">The squirrel got tired...</p> |
| |
| <div class="bg-amber-50 rounded-2xl p-4 mb-6 border-2 border-amber-200"> |
| <div class="text-sm text-amber-700 uppercase tracking-wide font-bold mb-1">Score</div> |
| <div id="finalScore" class="game-font text-4xl text-amber-600">0</div> |
| </div> |
|
|
| <button id="restartBtn" class="w-full bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-600 hover:to-teal-600 text-white game-font text-xl py-4 rounded-2xl shadow-lg transform transition hover:scale-105 active:scale-95 pointer-events-auto flex items-center justify-center gap-2"> |
| <i data-lucide="rotate-ccw" class="w-6 h-6"></i> |
| TRY AGAIN |
| </button> |
| </div> |
| </div> |
|
|
| <script> |
| |
| lucide.createIcons(); |
| |
| |
| const CANVAS = document.getElementById('gameCanvas'); |
| const CTX = CANVAS.getContext('2d'); |
| const GRAVITY = 0.6; |
| const JUMP_FORCE = -12; |
| const GROUND_HEIGHT = 100; |
| |
| |
| let gameState = 'start'; |
| let score = 0; |
| let highScore = localStorage.getItem('squirrelRushHighScore') || 0; |
| let gameSpeed = 5; |
| let frameCount = 0; |
| let particles = []; |
| |
| |
| const player = { |
| x: 100, |
| y: 0, |
| width: 50, |
| height: 50, |
| velocityY: 0, |
| isJumping: false, |
| jumpCount: 0, |
| maxJumps: 2, |
| color: '#D2691E', |
| emoji: '🐿️', |
| rotation: 0 |
| }; |
| |
| |
| let obstacles = []; |
| let acorns = []; |
| let clouds = []; |
| let trees = []; |
| |
| |
| const scoreDisplay = document.getElementById('scoreDisplay'); |
| const highScoreDisplay = document.getElementById('highScoreDisplay'); |
| const finalScore = document.getElementById('finalScore'); |
| const startScreen = document.getElementById('startScreen'); |
| const gameOverScreen = document.getElementById('gameOverScreen'); |
| const startBtn = document.getElementById('startBtn'); |
| const restartBtn = document.getElementById('restartBtn'); |
| const mobileHint = document.getElementById('mobileHint'); |
| |
| |
| const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); |
| if (isMobile) { |
| mobileHint.classList.remove('hidden'); |
| } |
| |
| |
| function init() { |
| resizeCanvas(); |
| highScoreDisplay.textContent = highScore; |
| |
| |
| for (let i = 0; i < 5; i++) { |
| clouds.push(createCloud()); |
| } |
| for (let i = 0; i < 8; i++) { |
| trees.push(createTree()); |
| } |
| |
| window.addEventListener('resize', resizeCanvas); |
| startBtn.addEventListener('click', startGame); |
| restartBtn.addEventListener('click', resetGame); |
| |
| |
| window.addEventListener('keydown', handleKeyDown); |
| window.addEventListener('touchstart', handleTouch, {passive: false}); |
| window.addEventListener('mousedown', handleMouse); |
| |
| gameLoop(); |
| } |
| |
| function resizeCanvas() { |
| CANVAS.width = window.innerWidth; |
| CANVAS.height = window.innerHeight; |
| player.y = CANVAS.height - GROUND_HEIGHT - player.height; |
| } |
| |
| function createCloud() { |
| return { |
| x: Math.random() * CANVAS.width, |
| y: Math.random() * (CANVAS.height / 2), |
| width: 60 + Math.random() * 100, |
| speed: 0.5 + Math.random() * 1, |
| opacity: 0.3 + Math.random() * 0.4 |
| }; |
| } |
| |
| function createTree() { |
| return { |
| x: Math.random() * CANVAS.width, |
| y: CANVAS.height - GROUND_HEIGHT, |
| width: 40 + Math.random() * 60, |
| height: 80 + Math.random() * 120, |
| type: Math.random() > 0.5 ? '🌲' : '🌳' |
| }; |
| } |
| |
| function createObstacle() { |
| const types = ['🪵', '🪨', '🌾']; |
| return { |
| x: CANVAS.width, |
| y: CANVAS.height - GROUND_HEIGHT - 40, |
| width: 40, |
| height: 40, |
| type: types[Math.floor(Math.random() * types.length)], |
| passed: false |
| }; |
| } |
| |
| function createAcorn() { |
| return { |
| x: CANVAS.width, |
| y: CANVAS.height - GROUND_HEIGHT - 80 - Math.random() * 150, |
| width: 30, |
| height: 30, |
| collected: false, |
| bobOffset: Math.random() * Math.PI * 2 |
| }; |
| } |
| |
| function createParticle(x, y, color) { |
| return { |
| x: x, |
| y: y, |
| vx: (Math.random() - 0.5) * 4, |
| vy: (Math.random() - 0.5) * 4, |
| life: 1, |
| color: color, |
| size: Math.random() * 4 + 2 |
| }; |
| } |
| |
| function handleKeyDown(e) { |
| if (e.code === 'Space' || e.code === 'ArrowUp') { |
| e.preventDefault(); |
| jump(); |
| } |
| } |
| |
| function handleTouch(e) { |
| if (gameState === 'playing') { |
| e.preventDefault(); |
| jump(); |
| } |
| } |
| |
| function handleMouse(e) { |
| if (gameState === 'playing' && e.target === CANVAS) { |
| jump(); |
| } |
| } |
| |
| function jump() { |
| if (gameState !== 'playing') return; |
| |
| if (player.jumpCount < player.maxJumps) { |
| player.velocityY = JUMP_FORCE; |
| player.isJumping = true; |
| player.jumpCount++; |
| player.rotation = -15; |
| |
| |
| for (let i = 0; i < 5; i++) { |
| particles.push(createParticle(player.x + player.width/2, player.y + player.height, '#8B4513')); |
| } |
| } |
| } |
| |
| function startGame() { |
| gameState = 'playing'; |
| startScreen.classList.add('hidden'); |
| score = 0; |
| gameSpeed = 5; |
| frameCount = 0; |
| obstacles = []; |
| acorns = []; |
| player.y = CANVAS.height - GROUND_HEIGHT - player.height; |
| player.velocityY = 0; |
| player.jumpCount = 0; |
| } |
| |
| function resetGame() { |
| gameOverScreen.classList.add('hidden'); |
| startGame(); |
| } |
| |
| function gameOver() { |
| gameState = 'gameover'; |
| |
| if (score > highScore) { |
| highScore = score; |
| localStorage.setItem('squirrelRushHighScore', highScore); |
| highScoreDisplay.textContent = highScore; |
| } |
| |
| finalScore.textContent = score; |
| gameOverScreen.classList.remove('hidden'); |
| |
| |
| document.body.classList.add('shake'); |
| setTimeout(() => document.body.classList.remove('shake'), 500); |
| } |
| |
| function update() { |
| if (gameState !== 'playing') return; |
| |
| frameCount++; |
| |
| |
| if (frameCount % 600 === 0) { |
| gameSpeed += 0.5; |
| } |
| |
| |
| player.velocityY += GRAVITY; |
| player.y += player.velocityY; |
| |
| |
| const groundY = CANVAS.height - GROUND_HEIGHT - player.height; |
| if (player.y >= groundY) { |
| player.y = groundY; |
| player.velocityY = 0; |
| player.isJumping = false; |
| player.jumpCount = 0; |
| player.rotation = 0; |
| } else { |
| |
| player.rotation += 2; |
| if (player.rotation > 20) player.rotation = 20; |
| } |
| |
| |
| if (frameCount % Math.floor(1200 / gameSpeed) === 0) { |
| obstacles.push(createObstacle()); |
| } |
| |
| |
| if (frameCount % 80 === 0 && Math.random() > 0.3) { |
| acorns.push(createAcorn()); |
| } |
| |
| |
| for (let i = obstacles.length - 1; i >= 0; i--) { |
| let obs = obstacles[i]; |
| obs.x -= gameSpeed; |
| |
| |
| if (!obs.passed && |
| player.x < obs.x + obs.width - 10 && |
| player.x + player.width > obs.x + 10 && |
| player.y < obs.y + obs.height - 10 && |
| player.y + player.height > obs.y + 10) { |
| gameOver(); |
| } |
| |
| |
| if (obs.x + obs.width < 0) { |
| obstacles.splice(i, 1); |
| score += 10; |
| scoreDisplay.textContent = score; |
| } |
| } |
| |
| |
| for (let i = acorns.length - 1; i >= 0; i--) { |
| let acorn = acorns[i]; |
| acorn.x -= gameSpeed; |
| acorn.bobOffset += 0.1; |
| |
| |
| if (!acorn.collected && |
| player.x < acorn.x + acorn.width && |
| player.x + player.width > acorn.x && |
| player.y < acorn.y + acorn.height && |
| player.y + player.height > acorn.y) { |
| |
| acorn.collected = true; |
| score += 50; |
| scoreDisplay.textContent = score; |
| |
| |
| for (let j = 0; j < 8; j++) { |
| particles.push(createParticle(acorn.x + acorn.width/2, acorn.y + acorn.height/2, '#FFD700')); |
| } |
| |
| acorns.splice(i, 1); |
| } else if (acorn.x + acorn.width < 0) { |
| acorns.splice(i, 1); |
| } else if (acorn.collected) { |
| acorns.splice(i, 1); |
| } |
| } |
| |
| |
| clouds.forEach(cloud => { |
| cloud.x -= cloud.speed; |
| if (cloud.x + cloud.width < 0) { |
| cloud.x = CANVAS.width; |
| cloud.y = Math.random() * (CANVAS.height / 2); |
| } |
| }); |
| |
| |
| trees.forEach(tree => { |
| tree.x -= gameSpeed * 0.5; |
| if (tree.x + tree.width < -100) { |
| tree.x = CANVAS.width + Math.random() * 200; |
| } |
| }); |
| |
| |
| for (let i = particles.length - 1; i >= 0; i--) { |
| let p = particles[i]; |
| p.x += p.vx; |
| p.y += p.vy; |
| p.vy += 0.2; |
| p.life -= 0.02; |
| |
| if (p.life <= 0) { |
| particles.splice(i, 1); |
| } |
| } |
| } |
| |
| function draw() { |
| |
| CTX.clearRect(0, 0, CANVAS.width, CANVAS.height); |
| |
| |
| clouds.forEach(cloud => { |
| CTX.save(); |
| CTX.globalAlpha = cloud.opacity; |
| CTX.fillStyle = '#FFFFFF'; |
| CTX.beginPath(); |
| CTX.arc(cloud.x, cloud.y, cloud.width/3, 0, Math.PI * 2); |
| CTX.arc(cloud.x + cloud.width/4, cloud.y - cloud.width/6, cloud.width/4, 0, Math.PI * 2); |
| CTX.arc(cloud.x + cloud.width/2, cloud.y, cloud.width/3, 0, Math.PI * 2); |
| CTX.fill(); |
| CTX.restore(); |
| }); |
| |
| |
| trees.forEach(tree => { |
| CTX.font = `${tree.width}px serif`; |
| CTX.textAlign = 'center'; |
| CTX.fillText(tree.type, tree.x, tree.y); |
| }); |
| |
| |
| const gradient = CTX.createLinearGradient(0, CANVAS.height - GROUND_HEIGHT, 0, CANVAS.height); |
| gradient.addColorStop(0, '#7CFC00'); |
| gradient.addColorStop(1, '#228B22'); |
| CTX.fillStyle = gradient; |
| CTX.fillRect(0, CANVAS.height - GROUND_HEIGHT, CANVAS.width, GROUND_HEIGHT); |
| |
| |
| CTX.fillStyle = '#32CD32'; |
| for (let i = 0; i < CANVAS.width; i += 50) { |
| const offset = (frameCount * gameSpeed + i) % CANVAS.width; |
| CTX.beginPath(); |
| CTX.arc(offset, CANVAS.height - GROUND_HEIGHT, 3, 0, Math.PI * 2); |
| CTX.arc(offset + 5, CANVAS.height - GROUND_HEIGHT - 2, 3, 0, Math.PI * 2); |
| CTX.arc(offset + 10, CANVAS.height - GROUND_HEIGHT, 3, 0, Math.PI * 2); |
| CTX.fill(); |
| } |
| |
| |
| acorns.forEach(acorn => { |
| const bob = Math.sin(acorn.bobOffset) * 5; |
| CTX.save(); |
| CTX.translate(acorn.x + acorn.width/2, acorn.y + acorn.height/2 + bob); |
| CTX.rotate(acorn.bobOffset); |
| CTX.font = '24px serif'; |
| CTX.textAlign = 'center'; |
| CTX.textBaseline = 'middle'; |
| |
| |
| CTX.shadowColor = '#FFD700'; |
| CTX.shadowBlur = 10; |
| CTX.fillText('🌰', 0, 0); |
| CTX.restore(); |
| }); |
| |
| |
| obstacles.forEach(obs => { |
| CTX.font = '32px serif'; |
| CTX.textAlign = 'center'; |
| CTX.textBaseline = 'middle'; |
| CTX.fillText(obs.type, obs.x + obs.width/2, obs.y + obs.height/2); |
| }); |
| |
| |
| CTX.save(); |
| CTX.translate(player.x + player.width/2, player.y + player.height/2); |
| CTX.rotate(player.rotation * Math.PI / 180); |
| CTX.font = '40px serif'; |
| CTX.textAlign = 'center'; |
| CTX.textBaseline = 'middle'; |
| |
| |
| CTX.fillText(player.emoji, 0, 5); |
| CTX.restore(); |
| |
| |
| particles.forEach(p => { |
| CTX.save(); |
| CTX.globalAlpha = p.life; |
| CTX.fillStyle = p.color; |
| CTX.beginPath(); |
| CTX.arc(p.x, p.y, p.size, 0, Math.PI * 2); |
| CTX.fill(); |
| CTX.restore(); |
| }); |
| } |
| |
| function gameLoop() { |
| update(); |
| draw(); |
| requestAnimationFrame(gameLoop); |
| } |
| |
| |
| init(); |
| </script> |
| <script src="https://deepsite.hf.co/deepsite-badge.js"></script> |
| </body> |
| </html> |