LIGHT OR DIE — Parkour
by MagicComet73521 lines13.9 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>LIGHT OR DIE — Parkour</title>
<style>
*{box-sizing:border-box}
body{margin:0;overflow:hidden;background:#080b18;color:white;font-family:Arial,sans-serif;touch-action:none;user-select:none}
canvas{display:block;width:100vw;height:100dvh;touch-action:none}
#hud{position:fixed;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}
.brand{font-weight:900;font-size:18px;letter-spacing:1px}
.info{font-size:12px;color:#bdc9e7;margin-top:6px;line-height:1.6}
#hud button{pointer-events:auto}
button{border:1px solid #ffffff25;border-radius:12px;padding:12px;background:#202942;color:white;font-weight:bold;font-size:14px;touch-action:none}
#switch{position:fixed;top:85px;left:50%;transform:translateX(-50%);border:2px solid #54dfff;background:#102c4a;color:#75e6ff;min-width:150px;font-size:15px}
#controls{position:fixed;bottom:14px;left:12px;right:12px;display:flex;justify-content:space-between;align-items:end;pointer-events:none}
.group{display:flex;gap:9px;pointer-events:auto}
.control{width:58px;height:58px;font-size:23px;background:#17243ded;border:1px solid #ffffff35}
#jump{width:82px;height:68px;background:#4b2c83;border-color:#b28bff;font-size:14px}
#switchMobile{width:76px;height:58px;font-size:12px;background:#12394b;border-color:#55dfff}
#overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#050815d9;padding:20px}
.panel{width:100%;max-width:370px;background:#111a30;border:1px solid #344567;border-radius:22px;padding:26px;text-align:center;box-shadow:0 15px 70px #0008}
.panel h1{font-size:32px;margin:0 0 12px;letter-spacing:1px}
.panel p{color:#b9c7e3;line-height:1.6;font-size:14px}
.primary{width:100%;margin-top:12px;background:#40d9ff;color:#07111d;border:0;font-size:16px}
.small{font-size:11px;color:#8395b9;margin-top:14px}
.hidden{display:none!important}
@media(min-width:800px){
#controls{max-width:650px;left:50%;transform:translateX(-50%)}
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="hud">
<div>
<div class="brand">⚡ LIGHT OR DIE</div>
<div class="info">LEVEL <span id="level">1</span> · CHECKPOINT <span id="cp">START</span><br>MODE: <span id="mode">LIGHTS ON</span></div>
</div>
<button id="restart">↻ RESTART</button>
</div>
<button id="switch">🔵 LIGHTS ON</button>
<div id="controls">
<div class="group">
<button class="control" id="left">◀</button>
<button class="control" id="right">▶</button>
</div>
<div class="group">
<button id="switchMobile">SWITCH<br>LIGHTS</button>
<button class="control" id="jump">JUMP ⤴</button>
</div>
</div>
<div id="overlay">
<div class="panel">
<h1 style="color:#55ddff">LIGHT <span style="color:#ff67dc">OR DIE</span></h1>
<p>Two paths. Two realities. Only one color is solid at a time.</p>
<p>🔵 Lights ON = blue platforms<br>🩷 Lights OFF = pink platforms<br><br>Jump across the gaps and switch colors in mid-air. Reach the green portal to win!</p>
<button class="primary" id="play">START PARKOUR</button>
<div class="small">MOVE: ARROWS / A-D · JUMP: SPACE · SWITCH: SHIFT</div>
</div>
</div>
<script>
(()=>{
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
const $=id=>document.getElementById(id);
let W=innerWidth,H=innerHeight,D=1;
function resize(){
W=innerWidth;H=innerHeight;
D=Math.min(devicePixelRatio||1,2);
canvas.width=W*D;canvas.height=H*D;
ctx.setTransform(D,0,0,D,0,0);
}
addEventListener("resize",resize);
resize();
const BLUE=0,PINK=1,NEUTRAL=2;
const COLORS={
blue:"#42dfff",pink:"#ff63d8",neutral:"#9eafcc"
};
let level=1,world=[],player,camera=0,light=BLUE;
let running=false,paused=false,frame=0;
let checkpointX=65,checkpointReached=false;
let spawnX=65,spawnY=0;
let particles=[];
let keys={left:false,right:false};
let jumpQueued=false,deadTimer=0,notice="",noticeTime=0;
let levelLength=0;
function floorY(){return Math.min(H*.70,H-150)}
function addPlatform(x,y,w,type,extra={}){
world.push({x,y,w,h:16,type,...extra});
}
function buildLevel(){
world=[];
const base=floorY();
const spacing=Math.max(138,Math.min(158,W*.40));
const count=12+Math.min(level,6);
levelLength=180+count*spacing+100;
// Safe starting platform
addPlatform(0,base,190,NEUTRAL);
addPlatform(190,base-25,82,BLUE);
addPlatform(190,base-25,82,PINK);
// Main route: alternating color paths and height
for(let i=1;i<=count;i++){
const x=190+i*spacing;
const y=base-25-((i%3)*24);
const type=i%2===1?PINK:BLUE;
const w=100;
addPlatform(x,y,w,type);
// Alternate path at a different height
if(i%2===0){
addPlatform(x+8,y-58,w-10,PINK);
}else{
addPlatform(x+8,y-58,w-10,BLUE);
}
// Occasional low alternate platform
if(i%3===0){
addPlatform(x+spacing*.48,base-4,72,i%2===0?PINK:BLUE);
}
}
// Permanent checkpoint island
const cpX=190+Math.floor(count*.53)*spacing;
checkpointX=cpX;
addPlatform(cpX,base-12,120,NEUTRAL,{checkpoint:true});
// Final safe runway and finish portal
const finishX=levelLength-100;
addPlatform(finishX-100,base-10,210,NEUTRAL,{finish:true});
addPlatform(finishX+100,base-10,120,NEUTRAL,{finish:true});
world.sort((a,b)=>a.x-b.x);
checkpointReached=false;
spawnX=65;
spawnY=base-32;
}
function start(){
level=1;
light=BLUE;
camera=0;
checkpointX=65;
checkpointReached=false;
spawnX=65;
particles=[];
buildLevel();
player={
x:spawnX,y:spawnY,w:23,h:32,
vx:0,vy:0,onGround:false,
coyote:0,jumpLock:false
};
running=true;paused=false;deadTimer=0;
$("overlay").classList.add("hidden");
updateHud();
}
function updateHud(){
$("level").textContent=level;
$("cp").textContent=checkpointReached?"SAVED":"START";
$("mode").textContent=light===BLUE?"LIGHTS ON":"LIGHTS OFF";
$("switch").textContent=light===BLUE?"🔵 LIGHTS ON":"🩷 LIGHTS OFF";
$("switch").style.borderColor=light===BLUE?COLORS.blue:COLORS.pink;
$("switch").style.color=light===BLUE?COLORS.blue:COLORS.pink;
}
function toggleLight(){
if(!running)return;
light=light===BLUE?PINK:BLUE;
updateHud();
burst(player.x+player.w/2,player.y+player.h/2,light===BLUE?COLORS.blue:COLORS.pink,7);
}
function activePlatform(p){
return p.type===NEUTRAL||p.type===light;
}
function respawn(){
player.x=spawnX;
player.y=spawnY;
player.vx=0;
player.vy=0;
player.onGround=false;
camera=Math.max(0,player.x-W*.30);
deadTimer=0;
notice="CHECKPOINT RESPAWN";
noticeTime=65;
}
function die(){
if(deadTimer>0)return;
deadTimer=35;
notice="YOU FELL!";
noticeTime=35;
}
function burst(x,y,color,n=12){
for(let i=0;i<n;i++){
particles.push({
x,y,vx:(Math.random()-.5)*5,
vy:(Math.random()-.7)*5,
life:25+Math.random()*20,color
});
}
}
function nextLevel(){
level++;
light=BLUE;
buildLevel();
player.x=65;
player.y=spawnY;
player.vx=0;
player.vy=0;
camera=0;
updateHud();
notice="LEVEL "+level+"!";
noticeTime=90;
burst(player.x,player.y,COLORS.blue,24);
}
function jump(){
if(!running||deadTimer>0)return;
if(player.onGround||player.coyote>0){
player.vy=-10.8;
player.onGround=false;
player.coyote=0;
burst(player.x+player.w/2,player.y+player.h,COLORS.blue,4);
}
}
function overlap(a,b){
return a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;
}
function update(){
frame++;
if(!running)return;
if(deadTimer>0){
deadTimer--;
if(deadTimer===0)respawn();
return;
}
// Smooth horizontal movement
const accel=.68,friction=.80,maxSpeed=5.0;
if(keys.left)player.vx-=accel;
if(keys.right)player.vx+=accel;
if(!keys.left&&!keys.right)player.vx*=friction;
player.vx=Math.max(-maxSpeed,Math.min(maxSpeed,player.vx));
// Gravity
const oldY=player.y;
player.vy=Math.min(player.vy+.48,12);
player.x+=player.vx;
player.y+=player.vy;
player.onGround=false;
// World boundaries
player.x=Math.max(0,player.x);
// Land only on currently active platforms.
// Inactive colored platforms are intangible.
if(player.vy>=0){
for(const p of world){
if(!activePlatform(p))continue;
const wasAbove=oldY+player.h<=p.y+4;
const nowCrossed=player.y+player.h>=p.y;
const horizontal=player.x+player.w>p.x&&player.x<p.x+p.w;
if(wasAbove&&nowCrossed&&horizontal){
player.y=p.y-player.h;
player.vy=0;
player.onGround=true;
if(p.checkpoint&&!checkpointReached){
checkpointReached=true;
spawnX=p.x+20;
spawnY=p.y-player.h;
updateHud();
notice="CHECKPOINT SAVED!";
noticeTime=85;
burst(p.x+55,p.y,COLORS.blue,18);
}
if(p.finish&&player.x+player.w>=levelLength-75){
nextLevel();
return;
}
}
}
}
if(player.onGround)player.coyote=7;
else player.coyote=Math.max(0,player.coyote-1);
// Check the finish region
if(player.x>levelLength-65&&player.y<H*.9){
nextLevel();
return;
}
// Fall detection
if(player.y>H+100)die();
// Camera follows player
const target=Math.max(0,player.x-W*.32);
camera+=(target-camera)*.12;
// Particles
for(const q of particles){
q.x+=q.vx;q.y+=q.vy;q.vy+=.12;q.life--;
}
particles=particles.filter(q=>q.life>0);
if(noticeTime>0)noticeTime--;
}
function roundRect(x,y,w,h,r,color){
ctx.fillStyle=color;
ctx.beginPath();
ctx.roundRect(x,y,w,h,r);
ctx.fill();
}
function drawPlatform(p){
const x=p.x-camera;
if(x+p.w< -30||x>W+30)return;
if(!activePlatform(p)){
// Hidden path: invisible, intangible
return;
}
const color=p.type===NEUTRAL?COLORS.neutral:
p.type===BLUE?COLORS.blue:COLORS.pink;
ctx.save();
ctx.shadowBlur=p.type===NEUTRAL?4:15;
ctx.shadowColor=color;
roundRect(x,p.y,p.w,p.h,5,color);
ctx.shadowBlur=0;
roundRect(x,p.y+p.h-4,p.w,4,3,
p.type===NEUTRAL?"#627594":
p.type===BLUE?"#1589bf":"#bc309c");
ctx.restore();
}
function drawPlayer(){
const x=player.x-camera,y=player.y;
const cx=x+player.w/2;
ctx.save();
ctx.shadowColor=light===BLUE?COLORS.blue:COLORS.pink;
ctx.shadowBlur=14;
// Head
roundRect(cx-7,y,14,14,5,"#ffffff");
// Eyes
ctx.fillStyle="#111a30";
ctx.fillRect(cx-4,y+5,2,3);
ctx.fillRect(cx+2,y+5,2,3);
// Body
roundRect(cx-5,y+14,10,11,"#ffcf65");
// Animated legs
const stride=player.onGround?Math.sin(frame*.25)*5:2;
ctx.strokeStyle="#ffffff";
ctx.lineWidth=4;
ctx.lineCap="round";
ctx.beginPath();
ctx.moveTo(cx-2,y+24);
ctx.lineTo(cx-5+stride,y+31);
ctx.moveTo(cx+2,y+24);
ctx.lineTo(cx+5-stride,y+31);
ctx.stroke();
// Arms
ctx.beginPath();
ctx.moveTo(cx-4,y+16);
ctx.lineTo(cx-9,y+21);
ctx.moveTo(cx+4,y+16);
ctx.lineTo(cx+9,y+21);
ctx.stroke();
ctx.restore();
}
function draw(){
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,"#080d20");
g.addColorStop(1,"#1a2340");
ctx.fillStyle=g;
ctx.fillRect(0,0,W,H);
// Stars
for(let i=0;i<55;i++){
const x=((i*113-camera*.13)%(W+20)+W+20)%(W+20);
const y=(i*71)%Math.max(H,1);
ctx.globalAlpha=.25+(i%3)*.2;
ctx.fillStyle="#b9dfff";
ctx.fillRect(x,y,2,2);
}
ctx.globalAlpha=1;
// Far skyline
for(let i=0;i<12;i++){
const x=((i*110-camera*.22)%(W+120)+W+120)%(W+120)-60;
ctx.fillStyle="#101a32";
ctx.fillRect(x,H*.72-((i*31)%90),70,H);
}
// Platforms
for(const p of world)drawPlatform(p);
// Checkpoint flag
const cp=world.find(p=>p.checkpoint);
if(cp){
const x=cp.x-camera+15;
ctx.fillStyle="#e7edf8";
ctx.fillRect(x,cp.y-43,3,31);
ctx.fillStyle=checkpointReached?"#52f0a3":"#ffd166";
ctx.beginPath();
ctx.moveTo(x+3,cp.y-42);
ctx.lineTo(x+23,cp.y-36);
ctx.lineTo(x+3,cp.y-29);
ctx.fill();
}
// Finish portal
const fx=levelLength-45-camera;
ctx.save();
ctx.shadowBlur=22;
ctx.shadowColor="#4dffac";
ctx.strokeStyle="#4dffac";
ctx.lineWidth=5;
ctx.beginPath();
ctx.ellipse(fx,floorY()-38,16,34,0,0,Math.PI*2);
ctx.stroke();
ctx.restore();
ctx.fillStyle="#4dffac";
ctx.font="bold 12px Arial";
ctx.textAlign="center";
ctx.fillText("FINISH",fx,floorY()-82);
ctx.textAlign="left";
drawPlayer();
// Particles
for(const q of particles){
ctx.globalAlpha=Math.max(0,q.life/40);
ctx.fillStyle=q.color;
ctx.fillRect(q.x-camera,q.y,4,4);
}
ctx.globalAlpha=1;
if(noticeTime>0){
ctx.textAlign="center";
ctx.font="bold 23px Arial";
ctx.fillStyle=notice.includes("FELL")?"#ff6689":
notice.includes("CHECKPOINT")?"#65ffb2":"#ffffff";
ctx.shadowColor="#000";
ctx.shadowBlur=8;
ctx.fillText(notice,W/2,H*.27);
ctx.shadowBlur=0;
ctx.textAlign="left";
}
}
function loop(){
update();
draw();
requestAnimationFrame(loop);
}
// Mobile buttons: hold left/right to move.
function holdButton(id,key){
const el=$(id);
el.addEventListener("pointerdown",e=>{
e.preventDefault();
keys[key]=true;
try{el.setPointerCapture(e.pointerId)}catch(_){}
});
const release=e=>{
e.preventDefault();
keys[key]=false;
};
el.addEventListener("pointerup",release);
el.addEventListener("pointercancel",release);
el.addEventListener("lostpointercapture",()=>keys[key]=false);
}
holdButton("left","left");
holdButton("right","right");
$("jump").addEventListener("pointerdown",e=>{
e.preventDefault();
jump();
});
$("switch").addEventListener("click",toggleLight);
$("switchMobile").addEventListener("pointerdown",e=>{
e.preventDefault();
toggleLight();
});
$("restart").addEventListener("click",()=>{
if(running){
buildLevel();
player.x=checkpointReached?spawnX:65;
player.y=checkpointReached?spawnY:floorY()-32;
player.vx=0;player.vy=0;
camera=Math.max(0,player.x-W*.32);
deadTimer=0;
}
});
$("play").addEventListener("click",start);
addEventListener("keydown",e=>{
if(["ArrowLeft","ArrowRight","ArrowUp"," ","Shift","a","d","A","D"].includes(e.key))e.preventDefault();
if(e.key==="ArrowLeft"||e.key==="a"||e.key==="A")keys.left=true;
if(e.key==="ArrowRight"||e.key==="d"||e.key==="D")keys.right=true;
if((e.key===" "||e.key==="ArrowUp")&&!e.repeat)jump();
if(e.key==="Shift"&&!e.repeat)toggleLight();
});
addEventListener("keyup",e=>{
if(e.key==="ArrowLeft"||e.key==="a"||e.key==="A")keys.left=false;
if(e.key==="ArrowRight"||e.key==="d"||e.key==="D")keys.right=false;
});
addEventListener("blur",()=>{keys.left=false;keys.right=false});
loop();
})();
</script>
</body>
</html>
Game Source: LIGHT OR DIE — Parkour
Creator: MagicComet73
Libraries: none
Complexity: complex (521 lines, 13.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: light-or-die-parkour-magiccomet73" to link back to the original. Then publish at arcadelab.ai/publish.