my-game / index.html
DzianisSISDZ's picture
Add 2 files
2318ba3 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stealth Ninja</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Arial', sans-serif;
}
body {
background-color: #121212;
color: #fff;
overflow: hidden;
height: 100vh;
}
/* Menu Styles */
.menu-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
z-index: 100;
transition: all 0.5s ease;
}
.menu-container.hidden {
opacity: 0;
pointer-events: none;
}
.game-title {
font-size: 4rem;
margin-bottom: 2rem;
background: linear-gradient(90deg, #f64f59, #c471ed, #12c2e9);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 20px rgba(198, 114, 237, 0.3);
}
.difficulty-options {
display: flex;
gap: 1.5rem;
margin-bottom: 3rem;
}
.difficulty-btn {
padding: 0.8rem 1.5rem;
border: none;
border-radius: 50px;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
text-transform: uppercase;
letter-spacing: 1px;
position: relative;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.difficulty-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
transform: translateX(-100%) skewX(-15deg);
transition: transform 0.4s ease;
}
.difficulty-btn:hover::before {
transform: translateX(100%) skewX(-15deg);
}
.easy {
background-color: #4caf50;
color: white;
}
.medium {
background-color: #ff9800;
color: white;
}
.hard {
background-color: #f44336;
color: white;
}
.start-btn {
padding: 1rem 2.5rem;
background: linear-gradient(90deg, #12c2e9, #c471ed);
border: none;
border-radius: 50px;
font-size: 1.5rem;
color: white;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
position: relative;
overflow: hidden;
z-index: 1;
}
.start-btn:hover {
transform: translateY(-5px);
box-shadow: 0 15px 25px rgba(0, 0, 0, 0.3);
}
.start-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(90deg, #c471ed, #12c2e9);
z-index: -1;
opacity: 0;
transition: opacity 0.3s ease;
}
.start-btn:hover::before {
opacity: 1;
}
/* Game Container */
.game-container {
width: 100%;
height: 100%;
position: relative;
display: none;
background-color: #1a1a2e;
overflow: hidden;
}
/* Game UI */
.game-ui {
position: absolute;
top: 1rem;
left: 1rem;
z-index: 10;
display: flex;
gap: 1rem;
}
.alert-box {
position: absolute;
top: 1rem;
left: 50%;
transform: translateX(-50%);
background-color: rgba(244, 67, 54, 0.8);
padding: 0.5rem 1rem;
border-radius: 5px;
font-size: 1rem;
font-weight: bold;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 20;
}
.alert-box.show {
opacity: 1;
}
.stealth-meter {
display: flex;
align-items: center;
gap: 0.5rem;
background-color: rgba(0, 0, 0, 0.5);
padding: 0.5rem 1rem;
border-radius: 50px;
}
.stealth-icon {
color: #4caf50;
}
.stealth-bar {
width: 100px;
height: 10px;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 5px;
overflow: hidden;
}
.stealth-fill {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #4caf50, #8bc34a);
transition: width 0.3s ease;
}
/* Game Elements */
.hideable {
position: absolute;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #333, #111);
display: flex;
justify-content: center;
align-items: center;
color: white;
z-index: 5;
user-select: none;
}
.ninja {
width: 30px;
height: 30px;
background: linear-gradient(135deg, #666, #333);
border-radius: 50%;
position: absolute;
z-index: 5;
transition: transform 0.3s ease;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.ninja i {
font-size: 14px;
}
.ninja.rolling {
animation: roll 0.5s linear;
transform: scale(0.8);
}
@keyframes roll {
0% { transform: rotate(0deg) scale(0.8); }
100% { transform: rotate(360deg) scale(0.8); }
}
.ninja.hidden {
opacity: 0.5;
transform: scale(0.7);
background: linear-gradient(135deg, #444, #222);
}
.guard {
width: 30px;
height: 30px;
background: linear-gradient(135deg, #a83232, #6b1c1c);
border-radius: 50%;
position: absolute;
z-index: 4;
display: flex;
justify-content: center;
align-items: center;
color: white;
}
.guard i {
font-size: 14px;
}
.vision-cone {
position: absolute;
width: 150px;
height: 150px;
background: linear-gradient(90deg, rgba(255, 0, 0, 0.1), rgba(255, 0, 0, 0.3));
clip-path: polygon(0 0, 100% 50%, 0 100%);
transform-origin: left center;
z-index: 3;
border-radius: 0 75px 75px 0;
}
.wall {
position: absolute;
background: linear-gradient(135deg, #3a3a3a, #2a2a2a);
z-index: 2;
border: 1px solid #555;
}
.exit {
position: absolute;
width: 40px;
height: 40px;
background-color: rgba(76, 175, 80, 0.3);
border: 2px dashed #4caf50;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
color: #4caf50;
z-index: 1;
}
.game-over, .game-won {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 100;
color: white;
font-size: 2rem;
display: none;
}
.restart-btn {
margin-top: 2rem;
padding: 1rem 2rem;
background: linear-gradient(90deg, #c471ed, #12c2e9);
border: none;
border-radius: 5px;
font-size: 1.2rem;
color: white;
cursor: pointer;
}
.controls {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.5);
padding: 0.5rem 1rem;
border-radius: 5px;
font-size: 0.8rem;
text-align: center;
}
.level-indicator {
position: absolute;
top: 1rem;
right: 1rem;
background-color: rgba(0, 0, 0, 0.5);
padding: 0.5rem 1rem;
border-radius: 50px;
font-size: 0.8rem;
}
</style>
</head>
<body>
<!-- Menu Screen -->
<div class="menu-container">
<h1 class="game-title">Stealth Ninja</h1>
<div class="difficulty-options">
<button class="difficulty-btn easy" data-difficulty="easy">
<i class="fas fa-leaf"></i> Easy
</button>
<button class="difficulty-btn medium" data-difficulty="medium">
<i class="fas fa-balance-scale"></i> Medium
</button>
<button class="difficulty-btn hard" data-difficulty="hard">
<i class="fas fa-fire"></i> Hard
</button>
</div>
<button class="start-btn">Start Mission</button>
</div>
<!-- Game Screen -->
<div class="game-container">
<div class="game-ui">
<div class="stealth-meter">
<i class="fas fa-user-ninja stealth-icon"></i>
<div class="stealth-bar">
<div class="stealth-fill"></div>
</div>
</div>
</div>
<div class="alert-box">Guard Alerted!</div>
<div class="level-indicator">Level: <span class="level-value">1</span></div>
<div class="controls">
WASD to move | SPACE to roll | SHIFT to hide | Avoid red vision cones
</div>
<div class="game-over">
<h2>Mission Failed</h2>
<p>You were detected!</p>
<button class="restart-btn">Try Again</button>
</div>
<div class="game-won">
<h2>Mission Complete</h2>
<p>You escaped undetected!</p>
<button class="restart-btn">Play Again</button>
</div>
</div>
<script>
// Game State
const gameState = {
difficulty: 'medium',
level: 1,
isGameActive: false,
ninja: {
x: 50,
y: 50,
speed: 2,
isHidden: false,
isRolling: false,
stealth: 100,
lastRollTime: 0,
rollCooldown: 1000
},
guards: [],
walls: [],
hideSpots: [],
exit: null,
keysPressed: {
w: false,
a: false,
s: false,
d: false,
shift: false,
space: false
},
lastTime: 0,
detectionLevel: 0,
maxDetection: 100
};
// DOM Elements
const menuContainer = document.querySelector('.menu-container');
const gameContainer = document.querySelector('.game-container');
const startBtn = document.querySelector('.start-btn');
const difficultyBtns = document.querySelectorAll('.difficulty-btn');
const stealthFill = document.querySelector('.stealth-fill');
const alertBox = document.querySelector('.alert-box');
const gameOverScreen = document.querySelector('.game-over');
const gameWonScreen = document.querySelector('.game-won');
const restartBtns = document.querySelectorAll('.restart-btn');
const levelValue = document.querySelector('.level-value');
// Initialize Game
function initGame() {
gameState.isGameActive = true;
gameContainer.style.display = 'block';
gameState.ninja.stealth = 100;
gameState.detectionLevel = 0;
updateStealthBar();
// Clear previous elements
document.querySelectorAll('.hideable, .ninja, .guard, .vision-cone, .wall, .exit').forEach(el => el.remove());
gameState.guards = [];
gameState.walls = [];
gameState.hideSpots = [];
createNinja();
generateLevel(gameState.level);
}
// Create Ninja Element
function createNinja() {
const ninja = document.createElement('div');
ninja.className = 'ninja';
ninja.innerHTML = '<i class="fas fa-user-ninja"></i>';
ninja.style.left = `${gameState.ninja.x}px`;
ninja.style.top = `${gameState.ninja.y}px`;
gameContainer.appendChild(ninja);
}
// Generate Level
function generateLevel(level) {
levelValue.textContent = level;
// Adjust difficulty based on level
const difficultyMultiplier = 1 + (level - 1) * 0.2;
// Create exit point
createExit(800, 450);
// Create walls
createWalls(level);
// Create hide spots
createHideSpots(level);
// Create guards based on difficulty
const guardCount = Math.min(3 + Math.floor(level / 2), 8);
for (let i = 0; i < guardCount; i++) {
createGuard(level, difficultyMultiplier);
}
}
function createExit(x, y) {
const exit = document.createElement('div');
exit.className = 'exit';
exit.innerHTML = '<i class="fas fa-door-open"></i>';
exit.style.left = `${x}px`;
exit.style.top = `${y}px`;
gameContainer.appendChild(exit);
gameState.exit = { x, y, element: exit };
}
function createWalls(level) {
// Border walls
addWall(0, 0, window.innerWidth, 20);
addWall(0, 0, 20, window.innerHeight);
addWall(0, window.innerHeight - 20, window.innerWidth, 20);
addWall(window.innerWidth - 20, 0, 20, window.innerHeight);
// Random walls based on level
const wallCount = 5 + level;
for (let i = 0; i < wallCount; i++) {
const width = 50 + Math.random() * 150;
const height = 30 + Math.random() * 100;
const x = 50 + Math.random() * (window.innerWidth - width - 100);
const y = 50 + Math.random() * (window.innerHeight - height - 100);
addWall(x, y, width, height);
}
}
function addWall(x, y, width, height) {
const wall = document.createElement('div');
wall.className = 'wall';
wall.style.left = `${x}px`;
wall.style.top = `${y}px`;
wall.style.width = `${width}px`;
wall.style.height = `${height}px`;
gameContainer.appendChild(wall);
gameState.walls.push({ x, y, width, height, element: wall });
}
function createHideSpots(level) {
const spotsCount = 3 + level;
for (let i = 0; i < spotsCount; i++) {
const x = 50 + Math.random() * (window.innerWidth - 100);
const y = 50 + Math.random() * (window.innerHeight - 100);
// Make sure it's not on a wall
if (!isPositionOnWall(x, y)) {
const spot = document.createElement('div');
spot.className = 'hideable';
spot.innerHTML = '<i class="fas fa-mountain"></i>';
spot.style.left = `${x}px`;
spot.style.top = `${y}px`;
gameContainer.appendChild(spot);
gameState.hideSpots.push({ x, y, element: spot });
}
}
}
function createGuard(level, difficultyMultiplier) {
let x, y;
do {
x = 100 + Math.random() * (window.innerWidth - 200);
y = 100 + Math.random() * (window.innerHeight - 200);
} while (isPositionOnWall(x, y) || isTooCloseToNinja(x, y));
const speed = 0.5 + Math.random() * 0.5 * difficultyMultiplier;
const visionRange = 100 + Math.random() * 50;
const visionAngle = 60;
const patrolDistance = 100 + Math.random() * 100;
const guard = {
x, y,
speed,
direction: Math.random() * Math.PI * 2,
visionRange,
visionAngle,
patrolDistance,
initialX: x,
initialY: y,
path: [], // For more complex patrol paths
isAlerted: false,
alertTimer: 0
};
// Create guard element
const guardEl = document.createElement('div');
guardEl.className = 'guard';
guardEl.innerHTML = '<i class="fas fa-eye"></i>';
guardEl.style.left = `${x}px`;
guardEl.style.top = `${y}px`;
gameContainer.appendChild(guardEl);
guard.element = guardEl;
// Create vision cone
const cone = document.createElement('div');
cone.className = 'vision-cone';
cone.style.left = `${x + 15}px`;
cone.style.top = `${y - 50}px`;
cone.style.width = `${visionRange}px`;
cone.style.height = `${visionRange * 2}px`;
gameContainer.appendChild(cone);
guard.visionCone = cone;
gameState.guards.push(guard);
}
// Check if position is on a wall
function isPositionOnWall(x, y) {
return gameState.walls.some(wall =>
x >= wall.x && x <= wall.x + wall.width &&
y >= wall.y && y <= wall.y + wall.height
);
}
// Check if guard is too close to ninja spawn
function isTooCloseToNinja(x, y) {
const dx = x - gameState.ninja.x;
const dy = y - gameState.ninja.y;
return Math.sqrt(dx * dx + dy * dy) < 200;
}
// Update Stealth Meter
function updateStealthBar() {
const stealthPercentage = gameState.ninja.stealth;
stealthFill.style.width = `${stealthPercentage}%`;
if (stealthPercentage > 70) {
stealthFill.style.background = 'linear-gradient(90deg, #4caf50, #8bc34a)';
} else if (stealthPercentage > 30) {
stealthFill.style.background = 'linear-gradient(90deg, #ff9800, #ffc107)';
} else {
stealthFill.style.background = 'linear-gradient(90deg, #f44336, #ff5722)';
}
}
// Game Loop
function gameLoop(timestamp) {
if (!gameState.isGameActive) return;
const deltaTime = timestamp - gameState.lastTime;
gameState.lastTime = timestamp;
updateNinja(deltaTime);
updateGuards(deltaTime);
checkDetection();
checkExit();
requestAnimationFrame(gameLoop);
}
// Update Ninja Position
function updateNinja(deltaTime) {
const ninja = gameState.ninja;
const ninjaEl = document.querySelector('.ninja');
// Reset rolling state if animation finished
if (ninja.isRolling && Date.now() - ninja.lastRollTime > 500) {
ninja.isRolling = false;
ninjaEl.classList.remove('rolling');
}
// Calculate movement
let dx = 0, dy = 0;
if (gameState.keysPressed.w) dy -= ninja.speed;
if (gameState.keysPressed.s) dy += ninja.speed;
if (gameState.keysPressed.a) dx -= ninja.speed;
if (gameState.keysPressed.d) dx += ninja.speed;
// Rolling modifier
if (ninja.isRolling) {
dx *= 2;
dy *= 2;
}
// Apply movement if not completely stopped
if (dx !== 0 || dy !== 0) {
ninja.x += dx;
ninja.y += dy;
// Stealth drain when moving
if (!ninja.isHidden && !ninja.isRolling) {
ninja.stealth = Math.max(0, ninja.stealth - 0.05);
updateStealthBar();
}
} else {
// Stealth recovery when standing still
if (!ninja.isRolling) {
ninja.stealth = Math.min(100, ninja.stealth + 0.1);
updateStealthBar();
}
}
// Check wall collisions
ninja.x = Math.max(20, Math.min(window.innerWidth - 40, ninja.x));
ninja.y = Math.max(20, Math.min(window.innerHeight - 40, ninja.y));
// Update DOM element
ninjaEl.style.left = `${ninja.x}px`;
ninjaEl.style.top = `${ninja.y}px`;
// Check hide state
const isNearHideSpot = gameState.hideSpots.some(spot => {
const dx = spot.x - ninja.x;
const dy = spot.y - ninja.y;
return Math.sqrt(dx * dx + dy * dy) < 30 && gameState.keysPressed.shift;
});
// Check wall hiding
const isNearWall = gameState.walls.some(wall => {
return (Math.abs(ninja.x - wall.x) < 15 ||
Math.abs(ninja.x - (wall.x + wall.width)) < 15) &&
ninja.y > wall.y && ninja.y < wall.y + wall.height &&
gameState.keysPressed.shift;
});
const shouldBeHidden = isNearHideSpot || isNearWall;
if (shouldBeHidden && !ninja.isHidden) {
ninja.isHidden = true;
ninjaEl.classList.add('hidden');
ninja.stealth = Math.min(100, ninja.stealth + 10);
updateStealthBar();
} else if (!shouldBeHidden && ninja.isHidden) {
ninja.isHidden = false;
ninjaEl.classList.remove('hidden');
}
// Roll cooldown
if (ninja.isRolling && Date.now() - ninja.lastRollTime > ninja.rollCooldown) {
ninja.isRolling = false;
}
}
// Update Guards
function updateGuards(deltaTime) {
gameState.guards.forEach(guard => {
// Simple patrol behavior
const angle = Date.now() * 0.0005;
guard.direction = angle;
// Move guard in patrol pattern
guard.x = guard.initialX + Math.cos(angle) * guard.patrolDistance;
guard.y = guard.initialY + Math.sin(angle) * guard.patrolDistance;
// Update guard position
guard.element.style.left = `${guard.x}px`;
guard.element.style.top = `${guard.y}px`;
// Update vision cone
guard.visionCone.style.left = `${guard.x + 15}px`;
guard.visionCone.style.top = `${guard.y - guard.visionRange * 0.5}px`;
guard.visionCone.style.transform = `rotate(${guard.direction}rad)`;
// Alert behavior
if (guard.isAlerted) {
guard.alertTimer += deltaTime;
if (guard.alertTimer > 3000) {
guard.isAlerted = false;
guard.alertTimer = 0;
}
}
});
}
// Check Detection
function checkDetection() {
if (gameState.ninja.isHidden) return;
const ninja = gameState.ninja;
let isDetected = false;
gameState.guards.forEach(guard => {
// Calculate distance from guard to ninja
const dx = ninja.x - guard.x;
const dy = ninja.y - guard.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < guard.visionRange) {
// Calculate angle between guard's direction and ninja
const angleToNinja = Math.atan2(dy, dx);
let angleDiff = Math.abs(guard.direction - angleToNinja);
// Normalize angle difference
while (angleDiff > Math.PI) angleDiff -= Math.PI * 2;
angleDiff = Math.abs(angleDiff);
// Check if ninja is in vision cone
if (angleDiff < guard.visionAngle * Math.PI / 360) {
// Check line of sight (no walls in between)
const hasLineOfSight = !gameState.walls.some(wall => {
return lineIntersectsRectangle(
guard.x, guard.y, ninja.x, ninja.y,
wall.x, wall.y, wall.width, wall.height
);
});
if (hasLineOfSight) {
// Detection based on stealth level
const detectionChance = (1 - gameState.ninja.stealth / 100) * 0.8;
if (Math.random() < detectionChance || distance < 30) {
isDetected = true;
guard.isAlerted = true;
guard.alertTimer = 0;
// Increase detection level
if (gameState.detectionLevel < gameState.maxDetection) {
gameState.detectionLevel += 2;
}
}
}
}
}
});
// Show/hide alert
if (isDetected) {
if (!alertBox.classList.contains('show')) {
alertBox.classList.add('show');
setTimeout(() => alertBox.classList.remove('show'), 2000);
}
}
// Game over if fully detected
if (gameState.detectionLevel >= gameState.maxDetection) {
gameOver();
}
}
// Check Exit
function checkExit() {
const ninja = gameState.ninja;
const exit = gameState.exit;
const dx = exit.x - ninja.x;
const dy = exit.y - ninja.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
levelComplete();
}
}
// Helper function for line-rectangle intersection
function lineIntersectsRectangle(x1, y1, x2, y2, rx, ry, rw, rh) {
// Check if line is inside rectangle
if ((x1 >= rx && x1 <= rx + rw && y1 >= ry && y1 <= ry + rh) ||
(x2 >= rx && x2 <= rx + rw && y2 >= ry && y2 <= ry + rh)) {
return true;
}
// Check line against each edge of rectangle
return (
lineIntersectsLine(x1, y1, x2, y2, rx, ry, rx + rw, ry) || // top
lineIntersectsLine(x1, y1, x2, y2, rx + rw, ry, rx + rw, ry + rh) || // right
lineIntersectsLine(x1, y1, x2, y2, rx, ry + rh, rx + rw, ry + rh) || // bottom
lineIntersectsLine(x1, y1, x2, y2, rx, ry, rx, ry + rh) // left
);
}
function lineIntersectsLine(x1, y1, x2, y2, x3, y3, x4, y4) {
const uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) /
((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
const uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) /
((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1));
return uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1;
}
// Game Events
function gameOver() {
gameState.isGameActive = false;
gameOverScreen.style.display = 'flex';
// Remove all vision cones
document.querySelectorAll('.vision-cone').forEach(el => el.remove());
}
function levelComplete() {
gameState.isGameActive = false;
gameWonScreen.style.display = 'flex';
// Remove all vision cones
document.querySelectorAll('.vision-cone').forEach(el => el.remove());
// Increase level
gameState.level++;
}
// Event Listeners
difficultyBtns.forEach(btn => {
btn.addEventListener('click', () => {
gameState.difficulty = btn.dataset.difficulty;
// Update active button
difficultyBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
startBtn.addEventListener('click', () => {
menuContainer.classList.add('hidden');
setTimeout(() => {
menuContainer.style.display = 'none';
initGame();
gameState.lastTime = performance.now();
requestAnimationFrame(gameLoop);
}, 500);
});
restartBtns.forEach(btn => {
btn.addEventListener('click', () => {
gameOverScreen.style.display = 'none';
gameWonScreen.style.display = 'none';
// Move ninja back to start
gameState.ninja.x = 50;
gameState.ninja.y = 50;
gameState.ninja.isHidden = false;
gameState.ninja.isRolling = false;
const ninjaEl = document.querySelector('.ninja');
if (ninjaEl) {
ninjaEl.classList.remove('hidden', 'rolling');
}
initGame();
gameState.lastTime = performance.now();
requestAnimationFrame(gameLoop);
});
});
// Keyboard Controls
document.addEventListener('keydown', (e) => {
if (!gameState.isGameActive) return;
switch (e.key.toLowerCase()) {
case 'w': gameState.keysPressed.w = true; break;
case 'a': gameState.keysPressed.a = true; break;
case 's': gameState.keysPressed.s = true; break;
case 'd': gameState.keysPressed.d = true; break;
case 'shift': gameState.keysPressed.shift = true; break;
case ' ':
if (!gameState.ninja.isRolling && Date.now() - gameState.ninja.lastRollTime > gameState.ninja.rollCooldown) {
gameState.ninja.isRolling = true;
gameState.ninja.lastRollTime = Date.now();
document.querySelector('.ninja').classList.add('rolling');
}
gameState.keysPressed.space = true;
break;
}
});
document.addEventListener('keyup', (e) => {
switch (e.key.toLowerCase()) {
case 'w': gameState.keysPressed.w = false; break;
case 'a': gameState.keysPressed.a = false; break;
case 's': gameState.keysPressed.s = false; break;
case 'd': gameState.keysPressed.d = false; break;
case 'shift': gameState.keysPressed.shift = false; break;
case ' ': gameState.keysPressed.space = false; break;
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body>
</html>