🎮ArcadeLab

Neon Rush

by CyberFalcon70
781 lines21.9 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>Neon Rush</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#08101f;font-family:Arial,sans-serif}
canvas{display:block;width:100%;height:100%;touch-action:none}
button{
  border:0;border-radius:14px;padding:12px 20px;font-size:17px;font-weight:700;
  color:white;background:#19a7ff;box-shadow:0 5px 0 #0875b9;cursor:pointer
}
button:active{transform:translateY(3px);box-shadow:0 2px 0 #0875b9}
#ui{position:fixed;inset:0;pointer-events:none;color:white}
#topbar{
  position:absolute;top:12px;left:12px;right:12px;display:flex;
  justify-content:space-between;align-items:flex-start;z-index:5
}
.stat{
  background:rgba(0,0,0,.35);backdrop-filter:blur(5px);border:1px solid rgba(255,255,255,.18);
  border-radius:13px;padding:7px 11px;margin-bottom:5px;text-shadow:0 2px 3px #000
}
#leftStats{display:flex;gap:7px;flex-wrap:wrap}
#rightControls{display:flex;gap:7px;pointer-events:auto}
.smallBtn{padding:9px 12px;font-size:16px}
#overlay{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:rgba(3,7,20,.65);backdrop-filter:blur(5px);pointer-events:auto;z-index:10
}
.panel{
  width:min(92vw,500px);padding:30px;border-radius:25px;text-align:center;
  background:linear-gradient(145deg,rgba(20,35,68,.96),rgba(7,14,32,.97));
  border:1px solid rgba(255,255,255,.2);box-shadow:0 20px 70px rgba(0,0,0,.5)
}
h1{font-size:48px;margin:0 0 5px;letter-spacing:3px}
.subtitle{color:#8ddcff;margin:0 0 25px;font-size:16px}
.big{font-size:22px;margin:12px}
.hidden{display:none!important}
.controls{
  margin:20px 0;color:#d8e8ff;line-height:1.7;font-size:14px
}
#powerText{
  position:absolute;top:105px;left:50%;transform:translateX(-50%);
  font-weight:bold;text-shadow:0 2px 4px #000;white-space:nowrap
}
#touchHint{
  position:absolute;bottom:12px;left:50%;transform:translateX(-50%);
  color:rgba(255,255,255,.6);font-size:12px
}
</style>
</head>
<body>
<canvas id="game"></canvas>

<div id="ui">
  <div id="topbar">
    <div id="leftStats">
      <div class="stat">⭐ <span id="score">0</span></div>
      <div class="stat">🪙 <span id="coins">0</span></div>
      <div class="stat">🏆 <span id="high">0</span></div>
    </div>
    <div id="rightControls">
      <button class="smallBtn" id="pauseBtn">Ⅱ</button>
      <button class="smallBtn" id="muteBtn">🔊</button>
    </div>
  </div>
  <div id="powerText"></div>
  <div id="touchHint">Swipe ← → to move • Swipe ↑ to jump • Swipe ↓ to slide</div>

  <div id="overlay">
    <div class="panel" id="menuPanel">
      <h1>NEON RUSH</h1>
      <p class="subtitle">Run. Dodge. Collect. Survive.</p>
      <div class="big">🏆 Best: <span id="menuHigh">0</span></div>
      <button id="startBtn">START RUN</button>
      <div class="controls">
        ← → / A D &nbsp; Move<br>
        ↑ / W / SPACE &nbsp; Jump<br>
        ↓ / S &nbsp; Slide<br>
        P &nbsp; Pause
      </div>
    </div>

    <div class="panel hidden" id="pausePanel">
      <h1>PAUSED</h1>
      <p class="subtitle">Catch your breath.</p>
      <button id="resumeBtn">RESUME</button>
    </div>

    <div class="panel hidden" id="gameOverPanel">
      <h1>RUN OVER</h1>
      <p class="subtitle">The city keeps moving...</p>
      <div class="big">Score: <span id="finalScore">0</span></div>
      <div class="big">Coins: <span id="finalCoins">0</span></div>
      <div class="big">Best: <span id="finalHigh">0</span></div>
      <button id="restartBtn">RUN AGAIN</button>
    </div>
  </div>
</div>

<script>
"use strict";

const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");

let W=innerWidth,H=innerHeight,DPR=Math.min(devicePixelRatio||1,2);

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

const scoreEl=document.getElementById("score");
const coinsEl=document.getElementById("coins");
const highEl=document.getElementById("high");
const menuHigh=document.getElementById("menuHigh");
const powerText=document.getElementById("powerText");

let highScore=Number(localStorage.getItem("neonRushHigh")||0);
highEl.textContent=highScore;
menuHigh.textContent=highScore;

let state="menu";
let score=0,coins=0,distance=0;
let speed=7;
let lane=1,targetLane=1;
let playerY=0,vy=0;
let slideTimer=0;
let spawnTimer=0;
let sceneryTimer=0;
let worldTime=0;
let shake=0;
let lastTime=0;
let muted=false;

let magnetTimer=0,shieldTimer=0,boostTimer=0;
let objects=[];
let particles=[];
let buildings=[];
let stars=[];

for(let i=0;i<70;i++){
  stars.push({
    x:Math.random(),y:Math.random()*.65,
    r:Math.random()*1.8+.3,a:Math.random()*.7+.2
  });
}

function roadY(){
  return H*.78;
}

function roadTop(){
  return H*.30;
}

function laneX(l,z){
  const t=Math.max(0,Math.min(1,z));
  const center=W/2;
  const roadWidthTop=Math.min(W*.22,190);
  const roadWidthBottom=Math.min(W*.92,700);
  const width=roadWidthTop+(roadWidthBottom-roadWidthTop)*t;
  return center+(l-1)*(width/3);
}

function depthY(z){
  return roadTop()+(roadY()-roadTop())*z;
}

function resetBuildings(){
  buildings=[];
  for(let i=0;i<24;i++){
    const side=i%2===0?-1:1;
    buildings.push({
      side,
      z:Math.random(),
      w:35+Math.random()*75,
      h:80+Math.random()*190,
      hue:Math.random()*360
    });
  }
}
resetBuildings();

function resetGame(){
  state="playing";
  score=0;coins=0;distance=0;
  speed=7;
  lane=1;targetLane=1;
  playerY=0;vy=0;slideTimer=0;
  spawnTimer=.4;
  objects=[];
  particles=[];
  magnetTimer=shieldTimer=boostTimer=0;
  shake=0;
  worldTime=0;
  resetBuildings();
  overlay("none");
  lastTime=performance.now();
  beep(220,.06,"triangle",.04);
}

function overlay(type){
  document.getElementById("overlay").classList.toggle("hidden",type==="none");
  document.getElementById("menuPanel").classList.toggle("hidden",type!=="menu");
  document.getElementById("pausePanel").classList.toggle("hidden",type!=="pause");
  document.getElementById("gameOverPanel").classList.toggle("hidden",type!=="over");
}

function endGame(){
  if(state!=="playing")return;
  state="over";
  const final=Math.floor(score);
  if(final>highScore){
    highScore=final;
    localStorage.setItem("neonRushHigh",highScore);
  }
  document.getElementById("finalScore").textContent=final;
  document.getElementById("finalCoins").textContent=coins;
  document.getElementById("finalHigh").textContent=highScore;
  highEl.textContent=highScore;
  beep(100,.35,"sawtooth",.07);
  overlay("over");
}

function pauseGame(){
  if(state==="playing"){
    state="paused";overlay("pause");
  }else if(state==="paused"){
    state="playing";overlay("none");lastTime=performance.now();
  }
}

document.getElementById("startBtn").onclick=()=>resetGame();
document.getElementById("restartBtn").onclick=()=>resetGame();
document.getElementById("resumeBtn").onclick=()=>pauseGame();
document.getElementById("pauseBtn").onclick=()=>pauseGame();

document.getElementById("muteBtn").onclick=()=>{
  muted=!muted;
  document.getElementById("muteBtn").textContent=muted?"🔇":"🔊";
};

function beep(freq,dur,type="sine",vol=.05){
  if(muted)return;
  try{
    const AC=window.AudioContext||window.webkitAudioContext;
    if(!window.audioCtx)window.audioCtx=new AC();
    const a=window.audioCtx;
    const o=a.createOscillator(),g=a.createGain();
    o.type=type;o.frequency.value=freq;
    g.gain.setValueAtTime(vol,a.currentTime);
    g.gain.exponentialRampToValueAtTime(.001,a.currentTime+dur);
    o.connect(g);g.connect(a.destination);
    o.start();o.stop(a.currentTime+dur);
  }catch(e){}
}

function moveLeft(){if(state!=="playing")return;targetLane=Math.max(0,targetLane-1);beep(300,.045,"square",.025)}
function moveRight(){if(state!=="playing")return;targetLane=Math.min(2,targetLane+1);beep(380,.045,"square",.025)}
function jump(){
  if(state!=="playing")return;
  if(playerY===0 && slideTimer<=0){
    vy=15.5;beep(520,.1,"triangle",.045);
  }
}
function slide(){
  if(state!=="playing")return;
  if(playerY===0){
    slideTimer=.65;beep(180,.08,"triangle",.035);
  }
}

addEventListener("keydown",e=>{
  if(["ArrowLeft","ArrowRight","ArrowUp","ArrowDown"," "].includes(e.key))e.preventDefault();
  if(e.key==="ArrowLeft"||e.key.toLowerCase()==="a")moveLeft();
  if(e.key==="ArrowRight"||e.key.toLowerCase()==="d")moveRight();
  if(e.key==="ArrowUp"||e.key.toLowerCase()==="w"||e.key===" ")jump();
  if(e.key==="ArrowDown"||e.key.toLowerCase()==="s")slide();
  if(e.key.toLowerCase()==="p")pauseGame();
});

let touchStartX=0,touchStartY=0;
canvas.addEventListener("touchstart",e=>{
  const t=e.changedTouches[0];
  touchStartX=t.clientX;touchStartY=t.clientY;
},{passive:true});

canvas.addEventListener("touchend",e=>{
  const t=e.changedTouches[0];
  const dx=t.clientX-touchStartX,dy=t.clientY-touchStartY;
  if(Math.max(Math.abs(dx),Math.abs(dy))<25)return;
  if(Math.abs(dx)>Math.abs(dy)){
    dx>0?moveRight():moveLeft();
  }else{
    dy<0?jump():slide();
  }
},{passive:true});

function spawn(type,l,z=-.02){
  objects.push({type,lane:l,z,phase:Math.random()*Math.PI*2});
}

function spawnWave(){
  const difficulty=Math.min(1,distance/1500);
  const r=Math.random();

  if(r<.46){
    const blocked=Math.floor(Math.random()*3);
    spawn(Math.random()<.55?"barrier":"crate",blocked);
    if(Math.random()<.8){
      const other=[0,1,2].filter(x=>x!==blocked);
      const good=other[Math.floor(Math.random()*other.length)];
      for(let i=0;i<4;i++)spawn("coin",good,-.12-i*.09);
    }
  }else if(r<.70){
    const l=Math.floor(Math.random()*3);
    for(let i=0;i<6;i++)spawn("coin",l,-.02-i*.075);
  }else if(r<.84){
    const l=Math.floor(Math.random()*3);
    spawn("highbar",l);
    for(let i=0;i<4;i++)spawn("coin",(l+1)%3,-.13-i*.09);
  }else if(r<.94){
    const l=Math.floor(Math.random()*3);
    spawn("power",l);
  }else{
    const safe=Math.floor(Math.random()*3);
    for(let l=0;l<3;l++){
      if(l!==safe)spawn("crate",l);
    }
    for(let i=0;i<5;i++)spawn("coin",safe,-.1-i*.075);
  }

  if(difficulty>.55 && Math.random()<.25){
    const l=Math.floor(Math.random()*3);
    spawn("crate",l,-.45);
  }
}

function update(dt){
  worldTime+=dt;
  distance+=speed*dt;
  speed=7+Math.min(8,distance/500);
  if(boostTimer>0)speed+=5;
  score+=speed*dt*2;

  targetLane=Math.max(0,Math.min(2,targetLane));
  lane+=(targetLane-lane)*Math.min(1,dt*12);

  if(playerY>0 || vy>0){
    playerY+=vy*dt;
    vy-=34*dt;
    if(playerY<=0){playerY=0;vy=0}
  }

  if(slideTimer>0)slideTimer-=dt;
  if(magnetTimer>0)magnetTimer-=dt;
  if(shieldTimer>0)shieldTimer-=dt;
  if(boostTimer>0)boostTimer-=dt;

  spawnTimer-=dt;
  const interval=Math.max(.36,.82-distance/4000);
  if(spawnTimer<=0){
    spawnWave();
    spawnTimer=interval*(.8+Math.random()*.35);
  }

  for(const o of objects)o.z+=speed*dt*.075;
  for(const b of buildings)b.z+=speed*dt*.012;
  for(const b of buildings)if(b.z>1.15)b.z=-.1;

  sceneryTimer+=dt;
  if(sceneryTimer>.3){
    sceneryTimer=0;
    if(Math.random()<.4){
      const side=Math.random()<.5?-1:1;
      buildings.push({
        side,z:-.05,w:35+Math.random()*70,h:80+Math.random()*190,
        hue:Math.random()*360
      });
    }
  }

  handleObjects();
  updateParticles(dt);

  if(shake>0)shake=Math.max(0,shake-dt*20);

  scoreEl.textContent=Math.floor(score);
  coinsEl.textContent=coins;
  highEl.textContent=Math.max(highScore,Math.floor(score));

  let p=[];
  if(magnetTimer>0)p.push("🧲 MAGNET "+magnetTimer.toFixed(1));
  if(shieldTimer>0)p.push("🛡️ SHIELD "+shieldTimer.toFixed(1));
  if(boostTimer>0)p.push("⚡ BOOST "+boostTimer.toFixed(1));
  powerText.textContent=p.join("   ");
}

function handleObjects(){
  const px=laneX(lane,1);
  for(let i=objects.length-1;i>=0;i--){
    const o=objects[i];
    if(o.z>1.08){objects.splice(i,1);continue}

    const x=laneX(o.lane,o.z);
    const y=depthY(o.z);
    const near=Math.abs(o.z-1)<.10;
    const laneHit=Math.abs(x-px)<Math.max(32,W*.035);

    if(o.type==="coin"){
      if(
        (near&&laneHit&&Math.abs(playerY)<55) ||
        (magnetTimer>0&&o.z>.65&&Math.abs(x-px)<130)
      ){
        collectCoin(o);
        objects.splice(i,1);
      }
      continue;
    }

    if(o.type==="power"){
      if(near&&laneHit&&Math.abs(playerY)<65){
        const types=["magnet","shield","boost"];
        const t=types[Math.floor(Math.random()*types.length)];
        if(t==="magnet")magnetTimer=8;
        if(t==="shield")shieldTimer=9;
        if(t==="boost")boostTimer=6;
        beep(700,.15,"sine",.06);
        burst(x,y-35,18,1.5);
        objects.splice(i,1);
      }
      continue;
    }

    if(near&&laneHit){
      let safe=false;
      if(o.type==="barrier"||o.type==="crate")safe=playerY>65;
      if(o.type==="highbar")safe=slideTimer>0;

      if(!safe){
        if(shieldTimer>0){
          shieldTimer=0;
          burst(x,y-40,25,2);
          shake=8;
          beep(150,.16,"sawtooth",.05);
          objects.splice(i,1);
        }else{
          shake=14;
          burst(x,y-35,35,2.5);
          endGame();
          return;
        }
      }
    }
  }
}

function collectCoin(o){
  coins++;
  score+=50;
  beep(800+Math.random()*250,.07,"sine",.045);
  const x=laneX(o.lane,Math.min(1,o.z));
  const y=depthY(Math.min(1,o.z));
  burst(x,y-30,12,1.2);
}

function burst(x,y,n,power){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2;
    const s=(30+Math.random()*100)*power;
    particles.push({
      x,y,
      vx:Math.cos(a)*s,vy:Math.sin(a)*s,
      life:.35+Math.random()*.5,
      max:.8,
      size:2+Math.random()*5
    });
  }
}

function updateParticles(dt){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i];
    p.x+=p.vx*dt;p.y+=p.vy*dt;
    p.vy+=90*dt;p.vx*=.97;
    p.life-=dt;
    if(p.life<=0)particles.splice(i,1);
  }
}

function skyGradient(){
  const cycle=(worldTime*.018)%1;
  const night=.5+.5*Math.sin(cycle*Math.PI*2-Math.PI/2);
  const g=ctx.createLinearGradient(0,0,0,H);
  const r1=Math.floor(10+night*20);
  const g1=Math.floor(20+night*18);
  const b1=Math.floor(55+night*65);
  const r2=Math.floor(50+night*30);
  const g2=Math.floor(100+night*30);
  const b2=Math.floor(160+night*30);
  g.addColorStop(0,`rgb(${r1},${g1},${b1})`);
  g.addColorStop(1,`rgb(${r2},${g2},${b2})`);
  return g;
}

function draw(){
  ctx.save();

  if(shake>0){
    ctx.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake);
  }

  ctx.fillStyle=skyGradient();
  ctx.fillRect(0,0,W,H);

  drawSky();
  drawBuildings();
  drawRoad();
  drawObjects();
  drawPlayer();
  drawParticles();

  ctx.restore();
}

function drawSky(){
  const cycle=(worldTime*.018)%1;
  const sunX=W*(.15+.7*cycle);
  const sunY=H*.16+Math.sin(cycle*Math.PI)*H*.07;

  ctx.globalAlpha=.75;
  ctx.fillStyle="#fff4ad";
  ctx.beginPath();ctx.arc(sunX,sunY,32,0,Math.PI*2);ctx.fill();
  ctx.globalAlpha=1;

  for(const s of stars){
    ctx.globalAlpha=s.a*(.3+.7*Math.abs(Math.sin(worldTime*.2)));
    ctx.fillStyle="white";
    ctx.beginPath();ctx.arc(s.x*W,s.y*H,s.r,0,Math.PI*2);ctx.fill();
  }
  ctx.globalAlpha=1;

  ctx.fillStyle="rgba(255,255,255,.08)";
  for(let i=0;i<4;i++){
    const x=((i*270+worldTime*5)%(W+300))-150;
    ctx.beginPath();
    ctx.ellipse(x,H*.22+i*22,90,18,0,0,Math.PI*2);
    ctx.fill();
  }
}

function drawBuildings(){
  const horizon=roadTop();
  for(const b of buildings){
    const z=Math.max(.05,Math.min(1,b.z));
    const scale=.35+z*1.15;
    const x=W/2+b.side*(W*.25+z*W*.24);
    const w=b.w*scale;
    const h=b.h*scale;
    const y=horizon-z*25;

    ctx.fillStyle=`hsl(${b.hue},28%,${15+z*14}%)`;
    ctx.fillRect(x-w/2,y-h,w,h);

    const rows=Math.max(2,Math.floor(h/25));
    const cols=Math.max(2,Math.floor(w/20));
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        if((r+c+Math.floor(worldTime*2))%3!==0){
          ctx.fillStyle=(Math.sin(worldTime*3+r+c)>0)
            ?"rgba(255,225,120,.65)":"rgba(80,190,255,.35)";
          ctx.fillRect(
            x-w/2+7+c*(w-12)/cols,
            y-h+9+r*(h-15)/rows,
            Math.max(3,w/cols-7),Math.max(4,h/rows-9)
          );
        }
      }
    }
  }
}

function drawRoad(){
  const top=roadTop(),bottom=roadY();
  ctx.fillStyle="#151a29";
  ctx.beginPath();
  ctx.moveTo(W*.5-W*.11,top);
  ctx.lineTo(W*.5+W*.11,top);
  ctx.lineTo(W*.5+W*.46,bottom+H*.2);
  ctx.lineTo(W*.5-W*.46,bottom+H*.2);
  ctx.closePath();ctx.fill();

  ctx.fillStyle="#292d3c";
  ctx.beginPath();
  ctx.moveTo(0,top);ctx.lineTo(W,top);
  ctx.lineTo(W,bottom+H*.2);ctx.lineTo(0,bottom+H*.2);
  ctx.closePath();ctx.fill();

  for(let l=1;l<3;l++){
    for(let i=0;i<12;i++){
      let z=((i/12)+(worldTime*speed*.018))%1;
      const y=depthY(z);
      const x=laneX(l,z);
      const w=2+z*5;
      ctx.fillStyle="rgba(120,210,255,.75)";
      ctx.fillRect(x-w/2,y,w,Math.max(3,z*14));
    }
  }

  for(let i=0;i<16;i++){
    let z=((i/16)+(worldTime*speed*.014))%1;
    const y=depthY(z);
    const x1=laneX(0,z),x2=laneX(2,z);
    ctx.fillStyle="rgba(255,255,255,.12)";
    ctx.fillRect(x1-2,y,4,Math.max(2,z*8));
    ctx.fillRect(x2-2,y,4,Math.max(2,z*8));
  }
}

function drawObjects(){
  const sorted=[...objects].sort((a,b)=>a.z-b.z);
  for(const o of sorted){
    const z=Math.max(.02,Math.min(1,o.z));
    const x=laneX(o.lane,z);
    const y=depthY(z);
    const s=.35+z*1.05;

    if(o.type==="coin"){
      const bob=Math.sin(worldTime*8+o.phase)*4*s;
      ctx.save();
      ctx.translate(x,y-35*s+bob);
      ctx.rotate(worldTime*5+o.phase);
      ctx.fillStyle="#ffd43b";
      ctx.beginPath();ctx.ellipse(0,0,12*s,12*s,0,0,Math.PI*2);ctx.fill();
      ctx.fillStyle="#fff2a3";
      ctx.beginPath();ctx.ellipse(-3*s,-3*s,4*s,7*s,0,0,Math.PI*2);ctx.fill();
      ctx.restore();
    }

    if(o.type==="crate"){
      const w=54*s,h=54*s;
      ctx.fillStyle="#b85d31";
      ctx.fillRect(x-w/2,y-h,w,h);
      ctx.strokeStyle="#f0a15a";ctx.lineWidth=4*s;
      ctx.strokeRect(x-w/2,y-h,w,h);
      ctx.beginPath();ctx.moveTo(x-w/2,y-h);ctx.lineTo(x+w/2,y);ctx.moveTo(x+w/2,y-h);ctx.lineTo(x-w/2,y);ctx.stroke();
    }

    if(o.type==="barrier"){
      const w=68*s,h=43*s;
      ctx.fillStyle="#ed4c55";
      ctx.fillRect(x-w/2,y-h,w,h);
      ctx.fillStyle="#ffe05a";
      for(let k=-1;k<=1;k++){
        ctx.save();ctx.translate(x+k*23*s,y-h/2);ctx.rotate(-.6);
        ctx.fillRect(-5*s,-18*s,10*s,36*s);ctx.restore();
      }
    }

    if(o.type==="highbar"){
      const w=74*s;
      ctx.strokeStyle="#ef4c75";ctx.lineWidth=10*s;
      ctx.beginPath();
      ctx.moveTo(x-w/2,y);ctx.lineTo(x-w/2,y-70*s);
      ctx.moveTo(x+w/2,y);ctx.lineTo(x+w/2,y-70*s);
      ctx.stroke();
      ctx.strokeStyle="#ffcb45";ctx.lineWidth=12*s;
      ctx.beginPath();ctx.moveTo(x-w/2,y-62*s);ctx.lineTo(x+w/2,y-62*s);ctx.stroke();
    }

    if(o.type==="power"){
      const bob=Math.sin(worldTime*6+o.phase)*5;
      ctx.save();ctx.translate(x,y-50*s+bob);
      ctx.fillStyle="#a86cff";
      ctx.shadowBlur=20;ctx.shadowColor="#a86cff";
      ctx.beginPath();ctx.arc(0,0,18*s,0,Math.PI*2);ctx.fill();
      ctx.shadowBlur=0;
      ctx.fillStyle="white";
      ctx.font=`bold ${20*s}px Arial`;
      ctx.textAlign="center";ctx.textBaseline="middle";
      ctx.fillText("★",0,1);
      ctx.restore();
    }
  }
}

function drawPlayer(){
  const x=laneX(lane,1);
  const base=roadY()-8;
  const jumpY=playerY;
  const sliding=slideTimer>0;

  ctx.save();
  ctx.translate(x,base-jumpY);

  const shadowSize=Math.max(8,35-playerY*.18);
  ctx.globalAlpha=.25;
  ctx.fillStyle="#000";
  ctx.beginPath();ctx.ellipse(0,8,shadowSize,8,0,0,Math.PI*2);ctx.fill();
  ctx.globalAlpha=1;

  if(shieldTimer>0){
    ctx.strokeStyle="rgba(80,220,255,.8)";
    ctx.lineWidth=4;
    ctx.shadowBlur=18;ctx.shadowColor="#38d9ff";
    ctx.beginPath();ctx.arc(0,-45,48,0,Math.PI*2);ctx.stroke();
    ctx.shadowBlur=0;
  }

  if(sliding){
    ctx.rotate(-.08);
    ctx.fillStyle="#28d7ff";
    roundRect(ctx,-30,-32,60,30,12);ctx.fill();
    ctx.fillStyle="#ffcc67";
    ctx.beginPath();ctx.arc(28,-29,12,0,Math.PI*2);ctx.fill();
    ctx.fillStyle="#18233f";
    ctx.fillRect(-24,-3,16,7);ctx.fillRect(8,-3,16,7);
  }else{
    ctx.fillStyle="#28d7ff";
    roundRect(ctx,-19,-63,38,45,12);ctx.fill();

    ctx.fillStyle="#ffcc67";
    ctx.beginPath();ctx.arc(0,-78,18,0,Math.PI*2);ctx.fill();

    ctx.fillStyle="#202943";
    ctx.fillRect(-15,-85,30,8);
    ctx.fillRect(-13,-76,7,5);ctx.fillRect(6,-76,7,5);

    ctx.fillStyle="#ff5c91";
    ctx.fillRect(-28,-55,10,29);
    ctx.fillRect(18,-55,10,29);

    ctx.fillStyle="#8b5cff";
    ctx.fillRect(-17,-18,12,25);
    ctx.fillRect(5,-18,12,25);
  }

  ctx.restore();
}

function roundRect(c,x,y,w,h,r){
  c.beginPath();
  c.moveTo(x+r,y);
  c.arcTo(x+w,y,x+w,y+h,r);
  c.arcTo(x+w,y+h,x,y+h,r);
  c.arcTo(x,y+h,x,y,r);
  c.arcTo(x,y,x+w,y,r);
  c.closePath();
}

function drawParticles(){
  for(const p of particles){
    ctx.globalAlpha=Math.max(0,p.life/p.max);
    ctx.fillStyle="#ffd84a";
    ctx.beginPath();ctx.arc(p.x,p.y,p.size,0,Math.PI*2);ctx.fill();
  }
  ctx.globalAlpha=1;
}

function loop(t){
  requestAnimationFrame(loop);
  const dt=Math.min(.035,(t-lastTime)/1000||0);
  lastTime=t;

  if(state==="playing")update(dt);
  draw();
}
requestAnimationFrame(loop);

overlay("menu");
</script>
</body>
</html>

Game Source: Neon Rush

Creator: CyberFalcon70

Libraries: none

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