squirrel-rush-3gb3y / index.html
CommanderLazarus's picture
Make me a game about squirrels.
75e841f verified
Raw
History Blame Contribute Delete
20.5 kB
<!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">
<!-- Game Canvas -->
<canvas id="gameCanvas" class="absolute inset-0 w-full h-full block"></canvas>
<!-- UI Overlay -->
<div id="uiLayer" class="absolute inset-0 pointer-events-none flex flex-col justify-between p-4">
<!-- Top Bar -->
<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>
<!-- Mobile Controls Hint -->
<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>
<!-- Start Screen -->
<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>
<!-- Game Over Screen -->
<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>
// Initialize Lucide icons
lucide.createIcons();
// Game Constants
const CANVAS = document.getElementById('gameCanvas');
const CTX = CANVAS.getContext('2d');
const GRAVITY = 0.6;
const JUMP_FORCE = -12;
const GROUND_HEIGHT = 100;
// Game State
let gameState = 'start'; // start, playing, gameover
let score = 0;
let highScore = localStorage.getItem('squirrelRushHighScore') || 0;
let gameSpeed = 5;
let frameCount = 0;
let particles = [];
// Player
const player = {
x: 100,
y: 0,
width: 50,
height: 50,
velocityY: 0,
isJumping: false,
jumpCount: 0,
maxJumps: 2,
color: '#D2691E',
emoji: '🐿️',
rotation: 0
};
// Obstacles & Items
let obstacles = [];
let acorns = [];
let clouds = [];
let trees = [];
// UI Elements
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');
// Check if mobile
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
mobileHint.classList.remove('hidden');
}
// Initialize
function init() {
resizeCanvas();
highScoreDisplay.textContent = highScore;
// Generate initial background elements
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);
// Controls
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;
// Create jump particles
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');
// Shake effect
document.body.classList.add('shake');
setTimeout(() => document.body.classList.remove('shake'), 500);
}
function update() {
if (gameState !== 'playing') return;
frameCount++;
// Increase speed gradually
if (frameCount % 600 === 0) {
gameSpeed += 0.5;
}
// Player physics
player.velocityY += GRAVITY;
player.y += player.velocityY;
// Ground collision
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 {
// Rotate while jumping
player.rotation += 2;
if (player.rotation > 20) player.rotation = 20;
}
// Spawn obstacles
if (frameCount % Math.floor(1200 / gameSpeed) === 0) {
obstacles.push(createObstacle());
}
// Spawn acorns
if (frameCount % 80 === 0 && Math.random() > 0.3) {
acorns.push(createAcorn());
}
// Update obstacles
for (let i = obstacles.length - 1; i >= 0; i--) {
let obs = obstacles[i];
obs.x -= gameSpeed;
// Collision detection
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();
}
// Remove off-screen
if (obs.x + obs.width < 0) {
obstacles.splice(i, 1);
score += 10;
scoreDisplay.textContent = score;
}
}
// Update acorns
for (let i = acorns.length - 1; i >= 0; i--) {
let acorn = acorns[i];
acorn.x -= gameSpeed;
acorn.bobOffset += 0.1;
// Collection detection
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;
// Sparkle particles
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);
}
}
// Update clouds (parallax)
clouds.forEach(cloud => {
cloud.x -= cloud.speed;
if (cloud.x + cloud.width < 0) {
cloud.x = CANVAS.width;
cloud.y = Math.random() * (CANVAS.height / 2);
}
});
// Update trees
trees.forEach(tree => {
tree.x -= gameSpeed * 0.5;
if (tree.x + tree.width < -100) {
tree.x = CANVAS.width + Math.random() * 200;
}
});
// Update particles
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; // gravity
p.life -= 0.02;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
function draw() {
// Clear canvas
CTX.clearRect(0, 0, CANVAS.width, CANVAS.height);
// Draw clouds
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();
});
// Draw background trees
trees.forEach(tree => {
CTX.font = `${tree.width}px serif`;
CTX.textAlign = 'center';
CTX.fillText(tree.type, tree.x, tree.y);
});
// Draw ground
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);
// Draw ground details (grass tufts)
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();
}
// Draw acorns
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';
// Glow effect
CTX.shadowColor = '#FFD700';
CTX.shadowBlur = 10;
CTX.fillText('🌰', 0, 0);
CTX.restore();
});
// Draw obstacles
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);
});
// Draw player
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';
// Shadow
CTX.fillText(player.emoji, 0, 5);
CTX.restore();
// Draw particles
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);
}
// Start
init();
</script>
<script src="https://deepsite.hf.co/deepsite-badge.js"></script>
</body>
</html>