/* ================================
45
There's something quieter than sleep
Within this inner room!
It wears a sprig upon its breast—
And will not tell its name.
Some touch it, and some kiss it—
Some chafe its idle hand—
It has a simple gravity
I do not understand!
I would not weep if I were they—
How rude in one to sob!
Might scare the quiet fairy
Back to her native wood!
While simple-hearted neighbors
Chat of the "Early dead"—
We—prone to periphrasis
Remark that Birds have fled!
Emily Dickinson
================================*/
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
canvas.style.display = 'block';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
const GAME_STATE = {
PLAYING: 'playing',
GAME_OVER: 'game_over' // (ノಠ益ಠ)ノ
};
const PLATFORM_TYPE = {
NORMAL: 'normal',
DEADLY: 'deadly',
FALLING: 'falling'
};
const PLAYER_SIZE = 20;
const GRAVITY = 0.5;
const JUMP_FORCE = 12;
const MOVE_SPEED = 7;
const PLATFORM_HEIGHT = 20;
const MIN_PLATFORM_WIDTH = 100;
const MAX_PLATFORM_WIDTH = 300;
const MIN_PARTITION_SIZE = MAX_PLATFORM_WIDTH + 20;
const PARTITION_RATIO = 0.3;
const MIN_PLATFORM_SPACING = 100;
const DEADLY_BORDER_HEIGHT = 7;
const ENEMY_SPEED = 2;
const FALLING_PLATFORM_DELAY = 800;
const FALLING_PLATFORM_GRAVITY = 0.5;
const FPS = 60;
const FRAME_TIME = 1000 / FPS;
const PARTICLE_COUNT = 20;
const PARTICLE_SPEED = 4;
const PARTICLE_SIZE = 4;
const PARTICLE_LIFETIME = 25;
const DEATH_PARTICLE_COUNT = 60;
const DEATH_PARTICLE_SPEED = 10;
const DEATH_PARTICLE_SIZE = 4;
const DEATH_PARTICLE_LIFETIME = 50;
const DEATH_ANIMATION_DURATION = 55;
const PLATFORM_PARTICLE_COUNT = 30;
const PLATFORM_PARTICLE_SPEED = 8;
const PLATFORM_PARTICLE_SIZE = 4;
const PLATFORM_PARTICLE_LIFETIME = 40;
const HARD_MODE_TIME_LIMIT = 7; // seconds
const SUPER_HARD_MODE_TIME_LIMIT = 5;
let gameState = GAME_STATE.PLAYING;
let level = 1;
let platforms = [];
let enemies = [];
let particles = [];
let deathParticles = [];
let deathAnimationTimer = 0;
let frameCount = 0;
let lastFpsUpdate = 0;
let currentFps = 0;
let lastFrameTime = 0;
let accumulator = 0;
let player = {
x: PLAYER_SIZE,
y: window.innerHeight - PLAYER_SIZE * 2,
velocityX: 0,
velocityY: 0,
isJumping: false,
jumpsLeft: 2,
gravityMultiplier: 1,
isDead: false
};
let exit = {
x: window.innerWidth - PLAYER_SIZE * 2,
y: PLAYER_SIZE * 2,
size: PLAYER_SIZE
};
const keys = {};
window.addEventListener('keydown', e => keys[e.key] = true);
window.addEventListener('keyup', e => keys[e.key] = false);
const COLORS = {
BACKGROUND: '#E0E0E0',
PLATFORM: {
NORMAL: '#1A1A1A',
DEADLY: 'tomato',
FALLING: 'rgba(26, 26, 26, 0.5)'
},
PLAYER: {
NORMAL: '#1A1A1A',
INVERTED: '#4A90E2'
},
ENEMY: 'tomato',
EXIT: 'teal',
TEXT: '#1A1A1A',
GAME_OVER: {
OVERLAY: 'rgba(0, 0, 0, 0.7)',
TEXT: 'rgba(255, 255, 255, 0.75)'
},
DEADLY_BORDER: 'tomato'
};
function randomRange(min, max) {
return min + Math.random() * (max - min);
}
function createPartition(x, y, width, height) {
return {
x,
y,
width,
height,
platform: null,
left: null,
right: null
};
}
function splitPartition(partition, vertical) {
const splitPoint = randomRange(
vertical ? partition.width * PARTITION_RATIO : partition.height * PARTITION_RATIO,
vertical ? partition.width * (1 - PARTITION_RATIO) : partition.height * (1 - PARTITION_RATIO)
);
return {
left: vertical
? createPartition(partition.x, partition.y, splitPoint, partition.height)
: createPartition(partition.x, partition.y, partition.width, splitPoint),
right: vertical
? createPartition(partition.x + splitPoint, partition.y, partition.width - splitPoint, partition.height)
: createPartition(partition.x, partition.y + splitPoint, partition.width, partition.height - splitPoint)
};
}
function platformsOverlap(platform1, platform2) {
const buffer = 5;
return !(
platform1.x + platform1.width + buffer < platform2.x ||
platform1.x > platform2.x + platform2.width + buffer ||
platform1.y + platform1.height + buffer < platform2.y ||
platform1.y > platform2.y + platform2.height + buffer
);
}
function createParticle(x, y, velocityY) {
return {
// Randomly places particles around the player, within the player's width
x: x + PLAYER_SIZE / 2 + (Math.random() - 0.5) * PLAYER_SIZE,
// Put particles to the top or bottom of the player depending on the direction
// the player is moving
y: y + (velocityY > 0 ? 0 : PLAYER_SIZE),
// Zoot out to the left and right of the player
velocityX: (Math.random() - 0.5) * PARTICLE_SPEED * 2,
// Zoot up or down, matching the player's direction
velocityY: (Math.random() * PARTICLE_SPEED) * Math.sign(velocityY),
// Add some variety to the particle sizes
size: PARTICLE_SIZE + Math.random() * 2,
life: PARTICLE_LIFETIME,
initialOpacity: 0.3 + Math.random() * 0.7
};
}
function createDeathParticles(x, y) {
deathParticles = [];
for (let i = 0; i < DEATH_PARTICLE_COUNT; i++) {
const angle = (Math.PI * 2 * i) / DEATH_PARTICLE_COUNT;
deathParticles.push({
x: x + PLAYER_SIZE / 2,
y: y + PLAYER_SIZE / 2,
velocityX: Math.cos(angle) * DEATH_PARTICLE_SPEED * (0.5 + Math.random()),
velocityY: Math.sin(angle) * DEATH_PARTICLE_SPEED * (0.5 + Math.random()),
size: DEATH_PARTICLE_SIZE + Math.random() * 2,
life: DEATH_PARTICLE_LIFETIME,
initialOpacity: 0.6 + Math.random() * 0.4
});
}
deathAnimationTimer = DEATH_ANIMATION_DURATION;
}
function updateParticles() {
particles = particles.filter(particle => {
particle.x += particle.velocityX;
particle.y += particle.velocityY;
particle.life--;
return particle.life > 0;
});
}
function updateDeathParticles() {
if (deathAnimationTimer > 0) {
deathAnimationTimer--;
}
deathParticles = deathParticles.filter(particle => {
particle.x += particle.velocityX;
particle.y += particle.velocityY;
particle.velocityY += GRAVITY * 0.5; // GRAVITY! Is working against me...
particle.life--;
return particle.life > 0;
});
}
function createEnemy(platform) {
const startsOnTop = Math.random() < 0.5; // 50% chance to start on top
return {
x: platform.x,
y: startsOnTop ?
platform.y - PLAYER_SIZE :
platform.y + platform.height,
width: PLAYER_SIZE,
height: PLAYER_SIZE,
platform: platform,
direction: 1,
moveRight: true,
isOnTop: startsOnTop // Track where things started
};
}
function updateEnemies() {
enemies.forEach(enemy => {
enemy.x += ENEMY_SPEED * (enemy.moveRight ? 1 : -1);
if (enemy.moveRight && enemy.x + enemy.width > enemy.platform.x + enemy.platform.width) {
enemy.moveRight = false;
} else if (!enemy.moveRight && enemy.x < enemy.platform.x) {
enemy.moveRight = true;
}
enemy.y = (player.gravityMultiplier > 0) === enemy.isOnTop ?
enemy.platform.y - enemy.height :
enemy.platform.y + enemy.platform.height;
});
}
function generatePlatformsForPartition(partition) {
if (partition.width < MIN_PLATFORM_WIDTH || partition.height < PLATFORM_HEIGHT * 2) {
return [];
}
const platformCount = partition.width > MAX_PLATFORM_WIDTH * 1.5
? Math.floor(randomRange(1, 3))
: 1;
const newPlatforms = [];
const maxAttempts = 20;
for (let i = 0; i < platformCount; i++) {
let validPlatform = null;
let attempts = 0;
// Try to create a platform, but don't try too hard so that things get stuck
while (!validPlatform && attempts < maxAttempts) {
// Generate a random width for the platform, that is between a min and a max
const platformWidth = randomRange(
MIN_PLATFORM_WIDTH,
Math.min(MAX_PLATFORM_WIDTH, partition.width * 0.8)
);
// Generate the minimum x position, which is either the partition's start
// or a position based on the platform's number being placed (i/platformCount)
// This spreads the platforms out more evenly across the partition
const minX = Math.max(
partition.x,
partition.x + (partition.width * (i / platformCount))
);
// Along the same lines, calculate a max x position that accounts for platform width
const maxX = Math.min(
partition.x + partition.width - platformWidth,
partition.x + (partition.width * ((i + 1) / platformCount))
);
// Try to place a platform if there is a valid range
if (maxX > minX) {
// Create a candidate platform with a random position and properties
const candidatePlatform = {
x: randomRange(minX, maxX),
y: randomRange(
partition.y + PLATFORM_HEIGHT,
partition.y + partition.height - PLATFORM_HEIGHT * 2
),
width: platformWidth,
height: PLATFORM_HEIGHT,
// After level 1, there is a chance that a platform will be deadly, or falling!
type: (() => {
if (level > 1 && Math.random() < 0.2) return PLATFORM_TYPE.DEADLY;
if (level > 1 && Math.random() < 0.3) {
return PLATFORM_TYPE.FALLING;
}
return PLATFORM_TYPE.NORMAL;
})(),
fallTimer: null,
velocityY: 0
};
// Check if the platform overlaps with any existing platforms
let overlapping = false;
for (const existingPlatform of [...platforms, ...newPlatforms]) {
if (platformsOverlap(candidatePlatform, existingPlatform)) {
overlapping = true;
break;
}
}
// If there isn't an overlap the platform is valid!
// Place it!
if (!overlapping) {
validPlatform = candidatePlatform;
}
}
attempts++;
}
if (validPlatform) {
newPlatforms.push(validPlatform);
}
}
newPlatforms.forEach(platform => {
if (platform.type === PLATFORM_TYPE.NORMAL && Math.random() < 0.2) { // 20% chance
enemies.push(createEnemy(platform));
}
});
return newPlatforms;
}
function generateLevel() {
platforms = [];
enemies = [];
platforms.push({
x: 0,
y: window.innerHeight - PLATFORM_HEIGHT,
width: MIN_PLATFORM_WIDTH,
height: PLATFORM_HEIGHT,
type: PLATFORM_TYPE.NORMAL
});
platforms.push({
x: window.innerWidth - MIN_PLATFORM_WIDTH,
y: PLAYER_SIZE * 3,
width: MIN_PLATFORM_WIDTH,
height: PLATFORM_HEIGHT,
type: PLATFORM_TYPE.NORMAL
});
const horizontalSections = 3;
const verticalSections = 2;
const sectionWidth = window.innerWidth / horizontalSections;
const sectionHeight = (window.innerHeight - PLATFORM_HEIGHT * 4) / verticalSections;
function subdivide(node, depth) {
if (depth === 0) {
return generatePlatformsForPartition(node);
}
const vertical = Math.random() > 0.4;
if ((vertical && node.width > MIN_PARTITION_SIZE * 1.5) ||
(!vertical && node.height > MIN_PARTITION_SIZE * 1.5)) {
const { left, right } = splitPartition(node, vertical);
return [
...subdivide(left, depth - 1),
...subdivide(right, depth - 1)
];
}
return generatePlatformsForPartition(node);
}
for (let i = 0; i < horizontalSections; i++) {
for (let j = 0; j < verticalSections; j++) {
const root = createPartition(
i * sectionWidth,
PLATFORM_HEIGHT * 2 + (j * sectionHeight),
sectionWidth,
sectionHeight
);
const newPlatforms = subdivide(root, Math.min(3 + Math.floor(level / 2), 5));
platforms.push(...newPlatforms);
}
}
levelStartTime = Date.now();
}
function resetPlayer() {
player.x = PLAYER_SIZE;
player.y = window.innerHeight - PLATFORM_HEIGHT - PLAYER_SIZE;
player.velocityX = 0;
player.velocityY = 0;
player.isJumping = false;
player.jumpsLeft = 2;
player.gravityMultiplier = 1;
player.isDead = false;
gameState = GAME_STATE.PLAYING;
level = 1;
generateLevel();
enemies = [];
levelStartTime = Date.now();
}
function killPlayer() {
if (!player.isDead) {
createDeathParticles(player.x, player.y);
player.isDead = true;
gameState = GAME_STATE.GAME_OVER;
}
}
// Try to compensate for varying viewport widths
function calculateTimeLimit(isSuper) {
const baseLimit = isSuper ? SUPER_HARD_MODE_TIME_LIMIT : HARD_MODE_TIME_LIMIT;
if (canvas.width <= 2000) return baseLimit;
const extraWidth = canvas.width - 2000;
const extraSeconds = Math.floor(extraWidth / 1000) * (isSuper ? 0.5 : 1);
return baseLimit + extraSeconds;
}
function updatePlayer() {
if (gameState === GAME_STATE.GAME_OVER) {
if (deathAnimationTimer <= 0 && keys['Enter']) {
resetPlayer();
}
return;
}
const timeLimit = superHardMode ?
calculateTimeLimit(true) :
calculateTimeLimit(false);
if ((hardMode || superHardMode) && Date.now() - levelStartTime > timeLimit * 1000) {
killPlayer();
return;
}
if (keys['ArrowLeft']) player.velocityX = -MOVE_SPEED;
else if (keys['ArrowRight']) player.velocityX = MOVE_SPEED;
else player.velocityX = 0;
if ((keys['g'] || keys[' ']) && !player.lastGravityKey) {
player.gravityMultiplier *= -1;
player.velocityY = 0;
}
player.lastGravityKey = keys['g'] || keys[' '];
player.velocityY += GRAVITY * player.gravityMultiplier;
if (keys['ArrowUp'] && keys['ArrowUp'] !== player.lastJumpKey && player.jumpsLeft > 0) {
player.velocityY = -JUMP_FORCE * player.gravityMultiplier;
player.jumpsLeft--;
player.isJumping = true;
// Add particles for every jump
for (let i = 0; i < PARTICLE_COUNT; i++) {
particles.push(createParticle(player.x, player.y, player.velocityY));
}
}
player.lastJumpKey = keys['ArrowUp'];
player.x += player.velocityX;
player.y += player.velocityY;
player.isJumping = true;
for (let platform of platforms) {
if (player.x + PLAYER_SIZE > platform.x &&
player.x < platform.x + platform.width) {
let collision = false;
if (player.gravityMultiplier > 0) {
if (player.y + PLAYER_SIZE > platform.y &&
player.y + PLAYER_SIZE < platform.y + platform.height + player.velocityY) {
collision = true;
player.y = platform.y - PLAYER_SIZE;
}
} else {
if (player.y < platform.y + platform.height &&
player.y > platform.y + player.velocityY) {
collision = true;
player.y = platform.y + platform.height;
}
}
if (collision) {
if (platform.type === PLATFORM_TYPE.DEADLY) {
killPlayer();
} else {
if (platform.type === PLATFORM_TYPE.FALLING && !platform.fallTimer) {
platform.fallTimer = setTimeout(() => {
platform.isFalling = true;
}, FALLING_PLATFORM_DELAY);
}
player.velocityY = 0;
player.isJumping = false;
player.jumpsLeft = 2;
}
}
}
}
if (player.y <= DEADLY_BORDER_HEIGHT) {
if (player.x < canvas.width - exit.size - 300) {
killPlayer();
}
} else if (player.y + PLAYER_SIZE >= canvas.height - DEADLY_BORDER_HEIGHT) {
killPlayer();
}
if (player.x < 0) player.x = 0;
if (player.x + PLAYER_SIZE > canvas.width) player.x = canvas.width - PLAYER_SIZE;
if (player.y < 0) {
player.y = 0;
player.velocityY = 0;
}
if (player.y + PLAYER_SIZE > canvas.height) {
player.y = canvas.height - PLAYER_SIZE;
player.velocityY = 0;
}
if (player.x + PLAYER_SIZE > exit.x &&
player.x < exit.x + exit.size &&
player.y + PLAYER_SIZE > exit.y &&
player.y < exit.y + exit.size) {
level++;
generateLevel();
player.x = PLAYER_SIZE;
player.y = window.innerHeight - PLATFORM_HEIGHT - PLAYER_SIZE;
player.velocityY = 0;
player.velocityX = 0;
player.jumpsLeft = 2;
player.isJumping = false;
}
enemies.forEach(enemy => {
if (player.x < enemy.x + enemy.width &&
player.x + PLAYER_SIZE > enemy.x &&
player.y < enemy.y + enemy.height &&
player.y + PLAYER_SIZE > enemy.y) {
killPlayer();
}
});
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function draw(currentTime) {
if ((hardMode || superHardMode) && gameState === GAME_STATE.PLAYING) {
const timeLimit = superHardMode ?
calculateTimeLimit(true) :
calculateTimeLimit(false);
const timeElapsed = Date.now() - levelStartTime;
const timeRemaining = Math.max(0, timeLimit * 1000 - timeElapsed);
const progressRatio = timeRemaining / (timeLimit * 1000);
ctx.fillStyle = COLORS.BACKGROUND;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
const progressWidth = canvas.width * progressRatio;
ctx.fillRect(progressWidth, 0, canvas.width - progressWidth, canvas.height);
} else {
ctx.fillStyle = COLORS.BACKGROUND;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.fillStyle = COLORS.DEADLY_BORDER;
ctx.fillRect(0, 0, canvas.width - exit.size - 300, DEADLY_BORDER_HEIGHT);
ctx.fillRect(0, canvas.height - DEADLY_BORDER_HEIGHT, platforms[0].x, DEADLY_BORDER_HEIGHT);
ctx.fillRect(
platforms[0].x + platforms[0].width,
canvas.height - DEADLY_BORDER_HEIGHT,
canvas.width - (platforms[0].x + platforms[0].width),
DEADLY_BORDER_HEIGHT
);
for (let platform of platforms) {
ctx.fillStyle = platform.type === PLATFORM_TYPE.DEADLY ? COLORS.PLATFORM.DEADLY :
platform.type === PLATFORM_TYPE.FALLING ? COLORS.PLATFORM.FALLING :
COLORS.PLATFORM.NORMAL;
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
}
ctx.fillStyle = COLORS.EXIT;
ctx.fillRect(exit.x, exit.y, exit.size, exit.size);
if (!player.isDead) {
ctx.fillStyle = player.gravityMultiplier > 0 ? COLORS.PLAYER.NORMAL : COLORS.PLAYER.INVERTED;
ctx.fillRect(player.x, player.y, PLAYER_SIZE, PLAYER_SIZE);
}
ctx.fillStyle = COLORS.TEXT;
ctx.font = '20px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Level: ${level}`, 10, 30);
// Particles can have different opacities
particles.forEach(particle => {
const alpha = (particle.life / PARTICLE_LIFETIME) * particle.initialOpacity;
ctx.fillStyle = `rgba(26, 26, 26, ${alpha})`;
ctx.fillRect(particle.x, particle.y, particle.size, particle.size);
});
enemies.forEach(enemy => {
ctx.fillStyle = COLORS.ENEMY;
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height);
});
// Death particles x_x
deathParticles.forEach(particle => {
const alpha = (particle.life / DEATH_PARTICLE_LIFETIME) * particle.initialOpacity;
ctx.fillStyle = `rgba(26, 26, 26, ${alpha})`;
ctx.fillRect(particle.x, particle.y, particle.size, particle.size);
});
if (gameState === GAME_STATE.GAME_OVER && deathAnimationTimer <= 0) {
ctx.fillStyle = COLORS.GAME_OVER.OVERLAY;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = COLORS.GAME_OVER.TEXT;
ctx.font = '100px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2);
ctx.font = '24px Arial';
ctx.fillText('Press ENTER to restart', canvas.width / 2, canvas.height / 2 + 40);
ctx.textAlign = 'left';
}
// Show some help text on the first level so that folks know what to do
if (level === 1 && gameState === GAME_STATE.PLAYING) {
ctx.fillStyle = COLORS.TEXT;
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.fillText('Arrow keys move the player.', canvas.width / 2, canvas.height / 2 - 20);
ctx.fillText('Space bar reverses gravity.', canvas.width / 2, canvas.height / 2 + 20);
ctx.textAlign = 'left';
}
}
function gameLoop(currentTime) {
if (lastFrameTime === 0) {
lastFrameTime = currentTime;
lastFpsUpdate = currentTime;
}
const deltaTime = currentTime - lastFrameTime;
lastFrameTime = currentTime;
accumulator += deltaTime;
while (accumulator >= FRAME_TIME) {
updatePlayer();
updateEnemies();
updateParticles();
updateDeathParticles();
updatePlatforms();
accumulator -= FRAME_TIME;
}
draw(currentTime);
requestAnimationFrame(gameLoop);
}
function createPlatformParticles(platform) {
for (let i = 0; i < PLATFORM_PARTICLE_COUNT; i++) {
const angle = (Math.PI * 2 * i) / PLATFORM_PARTICLE_COUNT;
particles.push({
x: platform.x + platform.width / 2,
y: platform.y + platform.height / 2,
velocityX: Math.cos(angle) * PLATFORM_PARTICLE_SPEED * (0.5 + Math.random()),
velocityY: Math.sin(angle) * PLATFORM_PARTICLE_SPEED * (0.5 + Math.random()),
size: PLATFORM_PARTICLE_SIZE + Math.random() * 2,
life: PLATFORM_PARTICLE_LIFETIME,
initialOpacity: 0.6 + Math.random() * 0.4
});
}
}
function updatePlatforms() {
platforms.forEach(platform => {
if (platform.type === PLATFORM_TYPE.FALLING && platform.isFalling) {
platform.velocityY += FALLING_PLATFORM_GRAVITY * player.gravityMultiplier;
platform.y += platform.velocityY;
// Create particles when platform goes off screen
if ((player.gravityMultiplier > 0 && platform.y > canvas.height + 50) ||
(player.gravityMultiplier < 0 && platform.y < -50)) {
createPlatformParticles(platform);
}
}
});
// Remove platforms that have fallen off screen in either direction
platforms = platforms.filter(platform =>
platform.type !== PLATFORM_TYPE.FALLING ||
(player.gravityMultiplier > 0 ? platform.y < canvas.height + 100 : platform.y > -100)
);
}
let hardMode = false;
let superHardMode = false;
let levelStartTime = 0;
const hardModeButton = document.createElement('button');
hardModeButton.textContent = 'Hard Mode: OFF';
hardModeButton.style.position = 'fixed';
hardModeButton.style.left = '10px';
hardModeButton.style.top = '40px';
hardModeButton.style.padding = '5px 10px';
hardModeButton.style.backgroundColor = COLORS.PLATFORM.NORMAL;
hardModeButton.style.color = 'white';
hardModeButton.style.border = 'none';
hardModeButton.style.cursor = 'pointer';
document.body.appendChild(hardModeButton);
const superHardModeButton = document.createElement('button');
superHardModeButton.textContent = 'Super Hard Mode: OFF';
superHardModeButton.style.position = 'fixed';
superHardModeButton.style.left = '10px';
superHardModeButton.style.top = '70px';
superHardModeButton.style.padding = '5px 10px';
superHardModeButton.style.backgroundColor = COLORS.PLATFORM.NORMAL;
superHardModeButton.style.color = 'white';
superHardModeButton.style.border = 'none';
superHardModeButton.style.cursor = 'pointer';
document.body.appendChild(superHardModeButton);
hardModeButton.addEventListener('click', () => {
hardMode = !hardMode;
superHardMode = false;
hardModeButton.textContent = `Hard Mode: ${hardMode ? 'ON' : 'OFF'}`;
superHardModeButton.textContent = 'Super Hard Mode: OFF';
resetPlayer();
hardModeButton.blur();
});
superHardModeButton.addEventListener('click', () => {
superHardMode = !superHardMode;
hardMode = false;
superHardModeButton.textContent = `Super Hard Mode: ${superHardMode ? 'ON' : 'OFF'}`;
hardModeButton.textContent = 'Hard Mode: OFF';
resetPlayer();
superHardModeButton.blur();
});
document.body.style.margin = '0';
document.body.style.overflow = 'hidden';
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
generateLevel();
requestAnimationFrame(gameLoop);