CAR RACER THUNDER
by BoldRider35435 lines14.1 KB
ArcadeLab
<!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>CAR RACER THUNDER</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#050b18;color:white;font-family:Arial,sans-serif;overflow:hidden;touch-action:none}
#game{display:block;width:100vw;height:100dvh}
#hud{position:fixed;inset:0;pointer-events:none}
.top{display:flex;justify-content:space-between;padding:12px;gap:6px}
.panel{background:#07162bd9;border:1px solid #42d9ff;border-radius:12px;padding:8px 12px;text-align:center;min-width:72px;box-shadow:0 0 12px #00aaff55}
.small{font-size:10px;color:#8feaff;letter-spacing:1px}
.value{font-size:19px;font-weight:900;margin-top:4px}
#bar{height:7px;background:#17243a;border-radius:8px;overflow:hidden;margin-top:5px}
#nitroFill{height:100%;width:100%;background:linear-gradient(90deg,#00eaff,#b6ffff)}
#controls{position:fixed;bottom:20px;left:0;right:0;display:flex;justify-content:space-between;align-items:end;padding:0 18px;pointer-events:none}
.group{display:flex;gap:12px;align-items:end}
.ctrl{pointer-events:auto;width:66px;height:66px;border-radius:20px;border:2px solid #6deaff;background:#07182dcc;color:white;font-size:27px;font-weight:bold;box-shadow:0 0 15px #00aaff44;touch-action:none}
.ctrl:active{background:#087b9d}
#nitro{width:76px;height:58px;font-size:13px;color:#9fffff}
#overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:#020817d9;padding:20px}
.card{width:min(390px,100%);background:linear-gradient(145deg,#11294a,#07101f);border:2px solid #36dfff;border-radius:24px;padding:25px;text-align:center;box-shadow:0 0 40px #00aaff44}
h1{font-size:32px;margin:5px 0;color:#8cf4ff;text-shadow:0 0 18px #00cfff}
button{cursor:pointer}
.mainbtn{width:100%;padding:15px;margin-top:12px;border:0;border-radius:12px;background:linear-gradient(90deg,#00b8ff,#00f0bb);font-size:17px;font-weight:900;color:#041225}
#message{font-size:14px;color:#c8eaff;line-height:1.6}
#pause{position:fixed;right:12px;top:95px;pointer-events:auto;border:1px solid #64eaff;background:#07182dcc;color:white;border-radius:10px;padding:10px 14px}
@media(max-height:500px){.ctrl{width:53px;height:53px}.panel{padding:5px 8px}.value{font-size:15px}#controls{bottom:8px}.card{padding:14px}}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="hud">
<div class="top">
<div class="panel"><div class="small">SCORE</div><div class="value" id="score">0</div></div>
<div class="panel"><div class="small">DISTANCE</div><div class="value" id="distance">0 m</div></div>
<div class="panel"><div class="small">COINS</div><div class="value" id="coins">0</div></div>
<div class="panel"><div class="small">LEVEL</div><div class="value" id="level">1</div></div>
</div>
<button id="pause">Ⅱ PAUSE</button>
<div style="position:absolute;top:91px;left:12px;width:120px" class="panel">
<div class="small">NITRO BOOST</div>
<div id="bar"><div id="nitroFill"></div></div>
</div>
</div>
<div id="controls">
<div class="group">
<button class="ctrl" id="left">◀</button>
<button class="ctrl" id="right">▶</button>
</div>
<div class="group">
<button class="ctrl" id="brake">▼</button>
<button class="ctrl" id="nitro">⚡ NITRO</button>
</div>
</div>
<div id="overlay">
<div class="card">
<div style="font-size:45px">🏎️</div>
<h1>CAR RACER<br>THUNDER</h1>
<p id="message">Race through traffic!<br>Collect coins, use nitro and beat your record.</p>
<div style="font-size:13px;color:#8feaff">MOBILE RACING • TRAFFIC • LEVELS</div>
<button class="mainbtn" id="start">START RACING</button>
<button class="mainbtn" id="garage" style="background:linear-gradient(90deg,#764dff,#e04fff)">CHANGE CAR</button>
</div>
</div>
<script>
(() => {
"use strict";
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
const $=id=>document.getElementById(id);
let W=innerWidth,H=innerHeight,DPR=1;
function resize(){
W=innerWidth;H=innerHeight;
DPR=Math.min(devicePixelRatio||1,2);
canvas.width=Math.floor(W*DPR);
canvas.height=Math.floor(H*DPR);
canvas.style.width=W+"px";
canvas.style.height=H+"px";
ctx.setTransform(DPR,0,0,DPR,0,0);
}
addEventListener("resize",resize);resize();
let state="menu";
let score=0,coins=0,distance=0,level=1;
let speed=0.48,nitro=100;
let roadOffset=0,worldTime=0;
let traffic=[],pickups=[],particles=[];
let carChoice=0;
const carColors=["#00eaff","#ff315c","#ffd447","#9d72ff","#48ff82"];
let car={x:0,lean:0};
let input={left:false,right:false,brake:false,nitro:false};
let best=0;
try{best=Number(localStorage.getItem("crtBest")||0)}catch(e){}
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}
function rand(a,b){return a+Math.random()*(b-a)}
function roadWidth(y){
const t=clamp((y-H*.17)/(H*.86),0,1);
return W*(.12+t*.43);
}
function roadCenter(y){
return W/2+Math.sin(worldTime*.00025)*W*.025*
clamp((y-H*.17)/(H*.86),0,1);
}
function roadX(lane,y){
return roadCenter(y)+lane*roadWidth(y)*.31;
}
function projectY(z){
return H*.18+Math.pow(clamp(z,0,1),1.7)*H*.84;
}
function polygon(points,color){
ctx.fillStyle=color;ctx.beginPath();
ctx.moveTo(points[0][0],points[0][1]);
for(let i=1;i<points.length;i++)ctx.lineTo(points[i][0],points[i][1]);
ctx.closePath();ctx.fill();
}
function drawBackground(){
let sky=ctx.createLinearGradient(0,0,0,H*.65);
sky.addColorStop(0,"#06132e");
sky.addColorStop(.55,"#17668b");
sky.addColorStop(1,"#f4a26a");
ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
let sun=ctx.createRadialGradient(W*.72,H*.22,2,W*.72,H*.22,H*.16);
sun.addColorStop(0,"#fff4bd");sun.addColorStop(1,"#ffb64b00");
ctx.fillStyle=sun;ctx.fillRect(0,0,W,H*.5);
// distant mountains
polygon([[0,H*.43],[W*.12,H*.24],[W*.24,H*.42],
[W*.39,H*.27],[W*.53,H*.43],[W*.73,H*.25],
[W*.88,H*.4],[W,H*.29],[W,H*.57],[0,H*.57]],"#344a68");
// roadside trees and buildings
for(let i=0;i<18;i++){
let z=((i/18+roadOffset*.23)%1);
let y=projectY(z);
let spread=roadWidth(y)*.72+W*.04;
for(const side of [-1,1]){
let x=roadCenter(y)+side*spread;
let size=8+z*35;
ctx.fillStyle="#173d3d";
ctx.fillRect(x-size*.09,y-size*.2,size*.18,size*.7);
ctx.fillStyle=i%2?"#1c7959":"#286b4c";
ctx.beginPath();ctx.arc(x,y-size*.3,size*.48,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(x-size*.25,y-size*.1,size*.3,0,Math.PI*2);ctx.fill();
}
}
// ground
ctx.fillStyle="#267448";
ctx.fillRect(0,H*.45,W,H*.55);
}
function drawRoad(){
const top=H*.18,bottom=H*1.08;
const ct=roadCenter(top),cb=roadCenter(bottom);
const wt=roadWidth(top)/2,wb=roadWidth(bottom)/2;
polygon([[ct-wt-W*.02,top],[ct+wt+W*.02,top],
[cb+wb+W*.02,bottom],[cb-wb-W*.02,bottom]],"#e7e7dc");
polygon([[ct-wt,top],[ct+wt,top],[cb+wb,bottom],[cb-wb,bottom]],"#343d4a");
// road edge strips
polygon([[ct-wt,top],[ct-wt+wt*.035,top],
[cb-wb+wb*.035,bottom],[cb-wb,bottom]],"#ffcc42");
polygon([[ct+wt-wt*.035,top],[ct+wt,top],
[cb+wb,bottom],[cb+wb-wb*.035,bottom]],"#ffcc42");
// moving lane markings
for(let i=0;i<18;i++){
let z=((i/18+roadOffset)%1);
let y=projectY(z),y2=projectY(Math.min(1,z+.025));
let rw=roadWidth(y),rw2=roadWidth(y2);
for(let lane of [-1/3,1/3]){
polygon([[roadCenter(y)+rw*lane-rw*.008,y],
[roadCenter(y)+rw*lane+rw*.008,y],
[roadCenter(y2)+rw2*lane+rw2*.008,y2],
[roadCenter(y2)+rw2*lane-rw2*.008,y2]],"#f8f5df");
}
}
}
function drawCar(x,y,w,h,color,isPlayer=false){
ctx.save();ctx.translate(x,y);
// shadow
ctx.fillStyle="#0008";
ctx.beginPath();ctx.ellipse(0,h*.39,w*.6,h*.13,0,0,Math.PI*2);ctx.fill();
// tires
ctx.fillStyle="#10141b";
for(const side of [-1,1]){
ctx.fillRect(side*w*.39-w*.08,-h*.2,w*.16,h*.34);
ctx.fillRect(side*w*.39-w*.08,h*.14,w*.16,h*.3);
}
// body
polygon([[-w*.33,h*.4],[-w*.44,h*.1],[-w*.36,-h*.31],
[-w*.2,-h*.45],[w*.2,-h*.45],[w*.36,-h*.31],
[w*.44,h*.1],[w*.33,h*.4]],color);
// windshield
polygon([[-w*.27,-h*.19],[w*.27,-h*.19],
[w*.19,-h*.38],[-w*.19,-h*.38]],"#a8efff");
polygon([[-w*.29,-h*.13],[w*.29,-h*.13],
[w*.34,h*.07],[-w*.34,h*.07]],"#172a42");
// headlights and rear lights
ctx.fillStyle=isPlayer?"#aaffff":"#ff344a";
ctx.fillRect(-w*.32,h*.27,w*.17,h*.07);
ctx.fillRect(w*.15,h*.27,w*.17,h*.07);
ctx.fillStyle="#ffffff";
ctx.fillRect(-w*.3,-h*.37,w*.12,h*.04);
ctx.fillRect(w*.18,-h*.37,w*.12,h*.04);
if(isPlayer){
ctx.fillStyle="#ffffff";
ctx.font=`bold ${Math.max(7,w*.17)}px Arial`;
ctx.textAlign="center";ctx.fillText("THUNDER",0,h*.18);
}
ctx.restore();
}
function spawnTraffic(){
let lane=Math.floor(rand(-1.5,2))*1;
lane=clamp(lane,-1,1);
if(Math.random()<.25)lane=rand(-1,1);
traffic.push({
lane,z:rand(.02,.12),
color:["#e63b52","#f4b72e","#8b65f7","#f0f3f8","#30c8a1"][Math.floor(rand(0,5))],
factor:rand(.35,.65),passed:false
});
}
function spawnCoin(){
pickups.push({lane:Math.round(rand(-1,1)),z:.03,spin:rand(0,6)});
}
function burst(x,y,color){
for(let i=0;i<14;i++){
particles.push({x,y,vx:rand(-3,3),vy:rand(-4,1),
life:rand(15,32),color,size:rand(2,5)});
}
}
function drawWorld(){
drawBackground();
drawRoad();
for(const p of pickups){
const y=projectY(p.z),x=roadX(p.lane,y);
const r=5+p.z*15;
ctx.save();ctx.translate(x,y);ctx.rotate(worldTime*.004+p.spin);
ctx.fillStyle="#ffc928";ctx.strokeStyle="#fff2a0";ctx.lineWidth=3;
ctx.beginPath();ctx.ellipse(0,0,r*.72,r,0,0,Math.PI*2);ctx.fill();ctx.stroke();
ctx.fillStyle="#8b5700";ctx.font=`bold ${r}px Arial`;ctx.textAlign="center";
ctx.fillText("$",0,r*.35);ctx.restore();
}
traffic.slice().sort((a,b)=>a.z-b.z).forEach(t=>{
let y=projectY(t.z),x=roadX(t.lane,y);
let w=roadWidth(y)*.18,h=w*1.55;
drawCar(x,y,w,h,t.color,false);
});
let py=H*.81;
let px=roadCenter(py)+car.x*roadWidth(py)*.31;
let pw=roadWidth(py)*.19;
if(input.nitro&&nitro>0&&state==="playing"){
for(let i=0;i<3;i++){
ctx.fillStyle=i===0?"#aaffff":"#00aaff";
ctx.globalAlpha=rand(.3,.8);
ctx.beginPath();ctx.moveTo(px-pw*.18,py+pw*.5);
ctx.lineTo(px,py+pw*rand(1,2.3));
ctx.lineTo(px+pw*.18,py+pw*.5);ctx.fill();
}
ctx.globalAlpha=1;
}
drawCar(px,py,pw,pw*1.55,carColors[carChoice],true);
particles.forEach(p=>{
ctx.globalAlpha=clamp(p.life/30,0,1);
ctx.fillStyle=p.color;ctx.fillRect(p.x,p.y,p.size,p.size);
});
ctx.globalAlpha=1;
}
function update(dt){
worldTime+=dt;
if(state!=="playing")return;
const boost=input.nitro&&nitro>0&&!input.brake;
if(input.left)car.x-=dt*.0018*(1+speed);
if(input.right)car.x+=dt*.0018*(1+speed);
car.x=clamp(car.x,-1.05,1.05);
if(boost){nitro=Math.max(0,nitro-dt*.025)}
else nitro=Math.min(100,nitro+dt*.009);
speed=boost?.95:input.brake?.25:.48+Math.min(level*.012,.22);
distance+=speed*dt*.035;
score+=speed*dt*.055;
roadOffset=(roadOffset+speed*dt*.0006)%1;
level=1+Math.floor(distance/450);
$("nitroFill").style.width=nitro+"%";
if(Math.random()<dt*.0009*(1+level*.12))spawnTraffic();
if(Math.random()<dt*.0007)spawnCoin();
for(const t of traffic){
t.z+=dt*.00016*(.5+speed+t.factor*.5);
if(!t.passed&&t.z>.85){
t.passed=true;
score+=10;
}
if(t.z>.76&&t.z<.93&&Math.abs(car.x-t.lane)<.32){
burst(roadX(t.lane,H*.81),H*.81,"#ff5b52");
endGame();return;
}
}
traffic=traffic.filter(t=>t.z<1.08);
for(const p of pickups){
p.z+=dt*.00016*(.5+speed);
if(p.z>.77&&p.z<.91&&Math.abs(car.x-p.lane)<.3){
coins++;score+=50;p.z=2;
burst(roadX(p.lane,H*.8),H*.8,"#ffd34d");
}
}
pickups=pickups.filter(p=>p.z<1.05);
particles.forEach(p=>{
p.x+=p.vx;p.y+=p.vy;p.vy+=.08;p.life-=1;
});
particles=particles.filter(p=>p.life>0);
$("score").textContent=Math.floor(score);
$("distance").textContent=Math.floor(distance)+" m";
$("coins").textContent=coins;
$("level").textContent=level;
}
let last=0;
function loop(now){
const dt=Math.min(now-last||16,35);last=now;
update(dt);drawWorld();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function startGame(){
score=0;coins=0;distance=0;level=1;speed=.48;nitro=100;
roadOffset=0;worldTime=0;car.x=0;
traffic=[];pickups=[];particles=[];
input={left:false,right:false,brake:false,nitro:false};
state="playing";
$("overlay").style.display="none";
$("pause").textContent="Ⅱ PAUSE";
}
function endGame(){
if(state!=="playing")return;
state="over";
best=Math.max(best,Math.floor(score));
try{localStorage.setItem("crtBest",best)}catch(e){}
$("message").innerHTML=
"RACE FINISHED!<br>Score: <b>"+Math.floor(score)+
"</b><br>Coins: <b>"+coins+"</b><br>Best: <b>"+best+
"</b><br>Tap below to race again.";
$("start").textContent="RACE AGAIN";
$("overlay").style.display="flex";
}
$("start").addEventListener("click",startGame);
$("garage").addEventListener("click",()=>{
carChoice=(carChoice+1)%carColors.length;
$("message").textContent="Car color changed! Choose your favorite car and start racing.";
});
$("pause").addEventListener("click",()=>{
if(state==="playing"){
state="paused";$("pause").textContent="▶ RESUME";
$("message").textContent="Race paused. Ready to continue?";
$("start").textContent="RESUME RACE";
$("overlay").style.display="flex";
}else if(state==="paused"){
state="playing";$("overlay").style.display="none";
$("pause").textContent="Ⅱ PAUSE";
}
});
function bindHold(id,key){
const el=$(id);
const on=e=>{e.preventDefault();input[key]=true};
const off=e=>{e.preventDefault();input[key]=false};
el.addEventListener("pointerdown",on);
el.addEventListener("pointerup",off);
el.addEventListener("pointercancel",off);
el.addEventListener("pointerleave",off);
}
bindHold("left","left");
bindHold("right","right");
bindHold("brake","brake");
bindHold("nitro","nitro");
addEventListener("keydown",e=>{
if(["ArrowLeft","ArrowRight","ArrowDown","ArrowUp"," "].includes(e.key))e.preventDefault();
if(e.key==="ArrowLeft"||e.key==="a")input.left=true;
if(e.key==="ArrowRight"||e.key==="d")input.right=true;
if(e.key==="ArrowDown"||e.key==="s")input.brake=true;
if(e.key==="ArrowUp"||e.key===" ")input.nitro=true;
});
addEventListener("keyup",e=>{
if(e.key==="ArrowLeft"||e.key==="a")input.left=false;
if(e.key==="ArrowRight"||e.key==="d")input.right=false;
if(e.key==="ArrowDown"||e.key==="s")input.brake=false;
if(e.key==="ArrowUp"||e.key===" ")input.nitro=false;
});
})();
</script>
</body>
</html>Game Source: CAR RACER THUNDER
Creator: BoldRider35
Libraries: none
Complexity: complex (435 lines, 14.1 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: car-racer-thunder-boldrider35" to link back to the original. Then publish at arcadelab.ai/publish.