🎮ArcadeLab

NEON RUSH

by FrozenOwl79
414 lines11.0 KB
▶ Play
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>NEON RUSH</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#050510;font-family:Arial;color:white}
canvas{display:block;width:100%;height:100%;touch-action:none}
#hud{position:fixed;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;pointer-events:none;font-weight:bold;text-shadow:0 0 8px #00eaff}
.panel{background:#071022aa;border:1px solid #00d9ff66;border-radius:12px;padding:7px 10px}
#menu{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle,#10194d,#03030b 70%);text-align:center}
.box{width:min(90%,430px);padding:28px;border:1px solid #00eaff88;border-radius:24px;background:#071022dd;box-shadow:0 0 50px #0077ff55}
h1{font-size:52px;margin:0;color:#00eaff;text-shadow:0 0 15px #00eaff,0 0 35px #7b00ff}
h2{color:#ff28d7}
button{border:0;border-radius:14px;padding:14px 22px;margin:6px;background:#086dff;color:white;font-size:17px;font-weight:bold;box-shadow:0 0 15px #0077ff88}
button:active{transform:scale(.95)}
#controls{position:fixed;bottom:18px;left:0;right:0;display:flex;justify-content:space-between;padding:0 22px;pointer-events:none}
.ctrl{pointer-events:auto;width:72px;height:72px;border-radius:50%;background:#071a32aa;border:2px solid #00d9ff88;color:white;font-size:28px}
.nitro{border-color:#ff20d0;color:#ff20d0;width:90px;height:90px}
#gameover{display:none;position:fixed;inset:0;background:#03030ddd;align-items:center;justify-content:center;text-align:center}
.stat{font-size:19px;margin:10px}
</style>
</head>
<body>

<canvas id="game"></canvas>

<div id="hud">
 <div class="panel">🏁 <span id="score">0</span></div>
 <div class="panel">💎 <span id="coins">0</span></div>
 <div class="panel">⚡ <span id="nitro">100</span>%</div>
 <div class="panel">LV <span id="level">1</span></div>
</div>

<div id="menu">
 <div class="box">
  <h1>NEON<br>RUSH</h1>
  <p>HIGH SPEED • NEON CITY • POLICE CHASE</p>
  <button onclick="startGame()">START RUSH</button>
  <button onclick="upgrade()">UPGRADE CAR</button>
  <div id="up">Speed Lv 1 • Handling Lv 1 • Nitro Lv 1</div>
  <p>Best: <span id="best">0</span></p>
 </div>
</div>

<div id="controls">
 <div>
  <button class="ctrl" id="left">◀</button>
  <button class="ctrl" id="right">▶</button>
 </div>
 <button class="ctrl nitro" id="boost">⚡</button>
</div>

<div id="gameover">
 <div class="box">
  <h2>CRASHED</h2>
  <div class="stat">Score: <b id="finalScore">0</b></div>
  <div class="stat">Coins: <b id="finalCoins">0</b></div>
  <button onclick="startGame()">RACE AGAIN</button>
  <button onclick="backMenu()">MENU</button>
 </div>
</div>

<script>
const c=document.getElementById("game");
const ctx=c.getContext("2d");
let W,H,DPR;

function resize(){
 DPR=Math.min(devicePixelRatio||1,2);
 W=innerWidth;H=innerHeight;
 c.width=W*DPR;c.height=H*DPR;
 ctx.setTransform(DPR,0,0,DPR,0,0);
}
addEventListener("resize",resize);resize();

let running=false;
let score=0,coins=0,nitro=100,level=1;
let speed=7,roadY=0,spawn=0,coinSpawn=0;
let player={x:0,y:0,w:44,h:78,tilt:0};
let cars=[],gems=[],particles=[];
let keys={left:false,right:false,boost:false};
let high=+localStorage.neonHigh||0;

let upgrades=JSON.parse(localStorage.neonUp||'{"speed":1,"handling":1,"nitro":1}');

function updateUp(){
 document.getElementById("up").textContent=
 "Speed Lv "+upgrades.speed+" • Handling Lv "+upgrades.handling+
 " • Nitro Lv "+upgrades.nitro;
}
updateUp();
document.getElementById("best").textContent=high;

function upgrade(){
 let cost=500*upgrades.speed+500*upgrades.handling+500*upgrades.nitro;
 if((+localStorage.neonCoins||0)<cost){
  alert("Need "+cost+" coins from races.");
  return;
 }
 localStorage.neonCoins=(+localStorage.neonCoins||0)-cost;
 upgrades.speed++;upgrades.handling++;upgrades.nitro++;
 localStorage.neonUp=JSON.stringify(upgrades);
 updateUp();
}

function startGame(){
 document.getElementById("menu").style.display="none";
 document.getElementById("gameover").style.display="none";
 running=true;
 score=0;coins=0;nitro=100;level=1;
 speed=7+upgrades.speed*.45;
 roadY=0;spawn=0;coinSpawn=0;
 cars=[];gems=[];particles=[];
 player.x=W/2;player.y=H-145;
 requestAnimationFrame(loop);
}

function backMenu(){
 running=false;
 document.getElementById("gameover").style.display="none";
 document.getElementById("menu").style.display="flex";
}

function road(){
 let rw=Math.min(W*.72,520);
 let left=(W-rw)/2;
 return {left,right:left+rw,w:rw};
}

function drawCity(){
 let r=road();

 let g=ctx.createLinearGradient(0,0,0,H);
 g.addColorStop(0,"#05051a");
 g.addColorStop(1,"#151035");
 ctx.fillStyle=g;ctx.fillRect(0,0,W,H);

 // buildings
 for(let side=0;side<2;side++){
  let start=side? r.right+5:0;
  let end=side?W:r.left-5;
  let bw=42;
  for(let x=start;x<end;x+=bw+7){
   let seed=Math.floor(x/49);
   let bh=90+(Math.abs(seed*37)%170);
   ctx.fillStyle=side?"#101338":"#0d1230";
   ctx.fillRect(x,H-bh,bw,bh);

   for(let y=H-bh+12;y<H-10;y+=20){
    if((Math.floor(y/20)+seed)%3){
     ctx.fillStyle=(seed%2)?"#00eaff":"#ff20d0";
     ctx.globalAlpha=.45;
     ctx.fillRect(x+8,y,6,5);
     ctx.fillRect(x+24,y,6,5);
     ctx.globalAlpha=1;
    }
   }
  }
 }

 // road
 ctx.fillStyle="#080a12";
 ctx.fillRect(r.left,0,r.w,H);

 // glowing edges
 ctx.shadowBlur=15;ctx.shadowColor="#00eaff";
 ctx.strokeStyle="#00eaff";ctx.lineWidth=3;
 ctx.beginPath();ctx.moveTo(r.left,0);ctx.lineTo(r.left,H);ctx.stroke();
 ctx.shadowBlur=15;ctx.shadowColor="#ff20d0";
 ctx.strokeStyle="#ff20d0";
 ctx.beginPath();ctx.moveTo(r.right,0);ctx.lineTo(r.right,H);ctx.stroke();
 ctx.shadowBlur=0;

 // lane lines
 let lanes=4,lw=r.w/lanes;
 for(let i=1;i<lanes;i++){
  for(let y=-80+(roadY%120);y<H;y+=120){
   ctx.fillStyle="#ffffff55";
   ctx.fillRect(r.left+i*lw-2,y,4,65);
  }
 }
}

function car(x,y,color,police=false,scale=1){
 ctx.save();
 ctx.translate(x,y);
 ctx.shadowBlur=18;ctx.shadowColor=color;

 let w=44*scale,h=78*scale;

 ctx.fillStyle=color;
 ctx.beginPath();
 ctx.roundRect(-w/2,-h/2,w,h,9);
 ctx.fill();

 ctx.shadowBlur=0;
 ctx.fillStyle="#07101e";
 ctx.beginPath();
 ctx.roundRect(-w*.32,-h*.32,w*.64,h*.28,5);
 ctx.fill();
 ctx.beginPath();
 ctx.roundRect(-w*.32,h*.04,w*.64,h*.22,5);
 ctx.fill();

 ctx.fillStyle="#ddd";
 ctx.fillRect(-w*.43,-h*.36,5,11);
 ctx.fillRect(w*.32,-h*.36,5,11);

 ctx.fillStyle="#ff1744";
 ctx.fillRect(-w*.38,h*.30,7,7);
 ctx.fillRect(w*.22,h*.30,7,7);

 if(police){
  ctx.fillStyle="#fff";
  ctx.fillRect(-w*.16,-h*.48,w*.32,7);
  ctx.fillStyle="#00eaff";
  ctx.fillRect(-w*.16,-h*.48,w*.16,7);
  ctx.fillStyle="#ff1744";
  ctx.fillRect(0,-h*.48,w*.16,7);
 }
 ctx.restore();
}

function coin(x,y){
 ctx.save();
 ctx.translate(x,y);
 ctx.rotate(performance.now()/500);
 ctx.shadowBlur=15;ctx.shadowColor="#ffe600";
 ctx.fillStyle="#ffe600";
 ctx.beginPath();ctx.arc(0,0,12,0,Math.PI*2);ctx.fill();
 ctx.fillStyle="#8a5700";
 ctx.font="bold 13px Arial";
 ctx.textAlign="center";ctx.textBaseline="middle";
 ctx.fillText("$",0,1);
 ctx.restore();
}

function explosion(x,y){
 for(let i=0;i<18;i++){
  particles.push({
   x,y,
   vx:(Math.random()-.5)*8,
   vy:(Math.random()-.5)*8,
   life:1
  });
 }
}

function spawnCar(){
 let r=road(),lane=Math.floor(Math.random()*4);
 let x=r.left+r.w/4*lane+r.w/8;
 cars.push({
  x,y:-100,
  w:44,h:78,
  speed:3+Math.random()*4+level*.25,
  color:["#ff206e","#7b2cff","#00eaff","#ff8c00","#21ff72"][Math.floor(Math.random()*5)],
  police:level>=4&&Math.random()<.15
 });
}

function spawnCoin(){
 let r=road(),lane=Math.floor(Math.random()*4);
 gems.push({
  x:r.left+r.w/4*lane+r.w/8,
  y:-30,
  spin:0
 });
}

function hit(a,b){
 return Math.abs(a.x-b.x)<(a.w+b.w)/2*.72 &&
        Math.abs(a.y-b.y)<(a.h+b.h)/2*.72;
}

function loop(t){
 if(!running)return;

 ctx.clearRect(0,0,W,H);

 roadY+=speed;

 drawCity();

 // boost
 let boosting=keys.boost&&nitro>0;
 if(boosting){
  speed+=(12+upgrades.speed)-speed*.08;
  nitro-=.65;
  for(let i=0;i<2;i++)particles.push({
   x:player.x+(Math.random()-.5)*20,
   y:player.y+45,
   vx:(Math.random()-.5)*2,
   vy:5+Math.random()*5,
   life:1
  });
 }else{
  let target=7+upgrades.speed*.45+level*.12;
  speed+=(target-speed)*.03;
  nitro=Math.min(100,nitro+.08*upgrades.nitro);
 }

 // steering
 let steer=5+upgrades.handling*.7;
 if(keys.left){player.x-=steer;player.tilt=-.12}
 else if(keys.right){player.x+=steer;player.tilt=.12}
 else player.tilt*=.8;

 let r=road();
 player.x=Math.max(r.left+25,Math.min(r.right-25,player.x));

 // spawn
 spawn-=1;
 if(spawn<=0){
  spawn=Math.max(25,65-level*3);
  spawnCar();
 }
 coinSpawn--;
 if(coinSpawn<=0){
  coinSpawn=45+Math.random()*55;
  spawnCoin();
 }

 // cars
 for(let i=cars.length-1;i>=0;i--){
  let a=cars[i];
  a.y+=a.speed+speed*.35;
  car(a.x,a.y,a.color,a.police);

  if(hit(player,a)){
   explosion(player.x,player.y);
   endGame();
   return;
  }
  if(a.y>H+100)cars.splice(i,1);
 }

 // coins
 for(let i=gems.length-1;i>=0;i--){
  let q=gems[i];
  q.y+=speed;
  coin(q.x,q.y);
  if(Math.hypot(q.x-player.x,q.y-player.y)<30){
   coins++;
   score+=100;
   gems.splice(i,1);
   for(let n=0;n<6;n++)particles.push({
    x:q.x,y:q.y,vx:(Math.random()-.5)*4,vy:(Math.random()-.5)*4,life:1
   });
  }else if(q.y>H+30)gems.splice(i,1);
 }

 // player
 car(player.x,player.y,"#00eaff",false,1);

 // particles
 for(let i=particles.length-1;i>=0;i--){
  let p=particles[i];
  p.x+=p.vx;p.y+=p.vy;p.life-=.035;
  ctx.globalAlpha=Math.max(0,p.life);
  ctx.fillStyle="#00eaff";
  ctx.fillRect(p.x,p.y,4,4);
  ctx.globalAlpha=1;
  if(p.life<=0)particles.splice(i,1);
 }

 score+=Math.floor(speed*.08);
 level=1+Math.floor(score/2500);

 document.getElementById("score").textContent=score;
 document.getElementById("coins").textContent=coins;
 document.getElementById("nitro").textContent=Math.floor(nitro);
 document.getElementById("level").textContent=level;

 requestAnimationFrame(loop);
}

function endGame(){
 running=false;
 localStorage.neonCoins=(+localStorage.neonCoins||0)+coins;
 if(score>high){
  high=score;
  localStorage.neonHigh=high;
 }
 document.getElementById("finalScore").textContent=score;
 document.getElementById("finalCoins").textContent=coins;
 document.getElementById("gameover").style.display="flex";
}

function bind(id,key){
 let b=document.getElementById(id);
 b.addEventListener("pointerdown",e=>{e.preventDefault();keys[key]=true});
 b.addEventListener("pointerup",e=>{e.preventDefault();keys[key]=false});
 b.addEventListener("pointercancel",()=>keys[key]=false);
 b.addEventListener("pointerleave",()=>keys[key]=false);
}
bind("left","left");
bind("right","right");
bind("boost","boost");

addEventListener("keydown",e=>{
 if(e.key==="ArrowLeft"||e.key==="a")keys.left=true;
 if(e.key==="ArrowRight"||e.key==="d")keys.right=true;
 if(e.key===" "||e.key==="Shift")keys.boost=true;
});
addEventListener("keyup",e=>{
 if(e.key==="ArrowLeft"||e.key==="a")keys.left=false;
 if(e.key==="ArrowRight"||e.key==="d")keys.right=false;
 if(e.key===" "||e.key==="Shift")keys.boost=false;
});
</script>
</body>
</html>

Game Source: NEON RUSH

Creator: FrozenOwl79

Libraries: none

Complexity: complex (414 lines, 11.0 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: neon-rush-frozenowl79" to link back to the original. Then publish at arcadelab.ai/publish.