STREET RUSH *{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; user-select:none; } html,body{ margin:0; width:100%; height:100%; overflow:hidden; background:#111; font-family:Arial,sans-serif; } body{ touch-action:none; } canvas{ position:fixed; inset:0; width:100%; height:100%; } #menu{ position:fixed; inset:0; z-index:10; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:20px; background:linear-gradient(145deg,#111827,#020617); color:white; overflow:auto; } .logo{ font-size:42px; font-weight:900; letter-spacing:3px; text-shadow:0 5px 20px rgba(0,0,0,.7); } .subtitle{ margin-top:6px; opacity:.6; letter-spacing:4px; font-size:12px; } .card{ width:min(390px,92vw); margin-top:25px; padding:20px; border-radius:25px; background:rgba(255,255,255,.08); border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(18px); box-shadow:0 20px 60px rgba(0,0,0,.3); } .user{ display:flex; align-items:center; gap:10px; margin-bottom:15px; opacity:.9; } .avatar{ width:42px; height:42px; border-radius:50%; background:#2563eb; display:flex; align-items:center; justify-content:center; font-weight:900; } .record{ text-align:center; margin-bottom:16px; opacity:.9; } .record b{ display:block; font-size:30px; margin-top:3px; } .play{ width:100%; height:62px; border:0; border-radius:18px; color:white; font-size:21px; font-weight:900; background:linear-gradient(135deg,#22c55e,#16a34a); box-shadow:0 10px 30px rgba(34,197,94,.25); } .row{ display:flex; gap:10px; } .mode{ flex:1; height:48px; margin-top:11px; border:0; border-radius:14px; color:white; font-weight:800; background:rgba(255,255,255,.1); } .mode.active{ background:#2563eb; } .difficultyTitle{ margin-top:18px; font-size:16px; font-weight:900; text-align:center; } .difficulty{ display:flex; gap:7px; margin-top:9px; } .diff{ flex:1; height:46px; border:0; border-radius:13px; color:white; font-weight:900; font-size:12px; background:rgba(255,255,255,.09); } .diff.active{ background:#2563eb; } #leaderboard{ margin-top:18px; } .lbTitle{ font-size:19px; font-weight:900; margin-bottom:9px; } .lbRow{ display:flex; justify-content:space-between; align-items:center; padding:9px 11px; margin:4px 0; border-radius:11px; background:rgba(255,255,255,.06); font-size:14px; } #hud{ position:fixed; top:18px; left:50%; transform:translateX(-50%); z-index:5; width:90%; display:none; justify-content:space-between; color:white; font-weight:900; font-size:18px; text-shadow:0 2px 5px #000; } #controls{ position:fixed; left:0; right:0; bottom:58px; z-index:8; display:none; justify-content:space-between; padding:0 20px; pointer-events:none; } .controlGroup{ display:flex; gap:14px; pointer-events:auto; } .ctrl{ width:88px; height:88px; border:1px solid rgba(255,255,255,.2); border-radius:50%; background:rgba(20,20,20,.58); backdrop-filter:blur(10px); color:white; font-size:30px; } .ctrl:active{ transform:scale(.92); background:rgba(255,255,255,.25); } #gameOver{ position:fixed; inset:0; z-index:20; display:none; flex-direction:column; align-items:center; justify-content:center; color:white; background:rgba(0,0,0,.72); } #gameOver h1{ margin:0 0 10px; font-size:40px; } .finalScore{ font-size:24px; margin-bottom:25px; } .restart{ width:220px; height:58px; border:0; border-radius:17px; color:white; background:#2563eb; font-size:18px; font-weight:900; } @media(max-height:650px){ #controls{ bottom:35px; } .ctrl{ width:78px; height:78px; } .card{ margin-top:15px; } } STREET RUSH MOBILE RACING S Player Telegram Твой рекорд 0 ▶ ИГРАТЬ 🎯 СЛОЖНОСТЬ 🟢 ЛЁГКИЙ 🟡 СРЕДНИЙ 🔴 СЛОЖНЫЙ ☀️ День 🌙 Ночь 🏆 Лидерборд Загрузка... 🏆 0 ⚡ 0 ◀ ▶ ▼ ▲ GAME OVER Очки: 0 🔄 ЕЩЁ РАЗ /* TELEGRAM */ const tg=window.Telegram?.WebApp; if(tg){ tg.ready(); tg.expand(); } /* API */ const API_URL="https://street-rush.onvlisitsaprint.workers.dev"; /* USER */ function getTelegramUser(){ if( tg && tg.initDataUnsafe && tg.initDataUnsafe.user ){ return tg.initDataUnsafe.user; } return{ id:"local-player", first_name:"Player", username:"" }; } const telegramUser=getTelegramUser(); const playerName= telegramUser.first_name || telegramUser.username || "Player"; document.getElementById( "playerName" ).textContent=playerName; document.getElementById( "avatar" ).textContent= playerName[0].toUpperCase(); /* CANVAS */ const canvas=document.getElementById("game"); const ctx=canvas.getContext("2d"); let W=innerWidth; let H=innerHeight; function resize(){ W=innerWidth; H=innerHeight; canvas.width=W*devicePixelRatio; canvas.height=H*devicePixelRatio; canvas.style.width=W+"px"; canvas.style.height=H+"px"; ctx.setTransform( devicePixelRatio, 0, 0, devicePixelRatio, 0, 0 ); } addEventListener("resize",resize); resize(); /* SETTINGS */ let timeMode= localStorage.getItem("racingTime") || "day"; let record= Number( localStorage.getItem("racingRecord") || 0 ); let difficulty= localStorage.getItem( "racingDifficulty" ) || "easy"; document.getElementById( "menuRecord" ).textContent=record; /* DAY / NIGHT */ function setMode(mode){ timeMode=mode; localStorage.setItem( "racingTime", mode ); document.getElementById( "dayBtn" ).classList.toggle( "active", mode==="day" ); document.getElementById( "nightBtn" ).classList.toggle( "active", mode==="night" ); } document.getElementById( "dayBtn" ).onclick=()=>{ setMode("day"); }; document.getElementById( "nightBtn" ).onclick=()=>{ setMode("night"); }; setMode(timeMode); /* DIFFICULTY */ const difficultySettings={ easy:{ maxEnemies:1, spawnMin:120, spawnMax:170, enemySpeed:.70, aiMin:190, aiMax:290, aiSmooth:.018 }, medium:{ maxEnemies:2, spawnMin:80, spawnMax:120, enemySpeed:.88, aiMin:125, aiMax:210, aiSmooth:.025 }, hard:{ maxEnemies:4, spawnMin:48, spawnMax:78, enemySpeed:1.05, aiMin:65, aiMax:125, aiSmooth:.038 } }; function setDifficulty(mode){ difficulty=mode; localStorage.setItem( "racingDifficulty", mode ); document.getElementById( "easyBtn" ).classList.toggle( "active", mode==="easy" ); document.getElementById( "mediumBtn" ).classList.toggle( "active", mode==="medium" ); document.getElementById( "hardBtn" ).classList.toggle( "active", mode==="hard" ); } document.getElementById( "easyBtn" ).onclick=()=>{ setDifficulty("easy"); }; document.getElementById( "mediumBtn" ).onclick=()=>{ setDifficulty("medium"); }; document.getElementById( "hardBtn" ).onclick=()=>{ setDifficulty("hard"); }; setDifficulty(difficulty); /* GAME */ let running=false; let score=0; let speed=5; let player={ x:W/2, y:H-220, width:44, height:78 }; let enemies=[]; const lanes=[ -100, 0, 100 ]; const roadWidth=330; let roadOffset=0; let enemyTimer=30; /* INPUT */ let keys={ left:false, right:false, gas:false, brake:false }; addEventListener("keydown",e=>{ if( e.key==="ArrowLeft" || e.key.toLowerCase()==="a" ) keys.left=true; if( e.key==="ArrowRight" || e.key.toLowerCase()==="d" ) keys.right=true; if( e.key==="ArrowUp" || e.key.toLowerCase()==="w" ) keys.gas=true; if( e.key==="ArrowDown" || e.key.toLowerCase()==="s" ) keys.brake=true; }); addEventListener("keyup",e=>{ if( e.key==="ArrowLeft" || e.key.toLowerCase()==="a" ) keys.left=false; if( e.key==="ArrowRight" || e.key.toLowerCase()==="d" ) keys.right=false; if( e.key==="ArrowUp" || e.key.toLowerCase()==="w" ) keys.gas=false; if( e.key==="ArrowDown" || e.key.toLowerCase()==="s" ) keys.brake=false; }); /* MOBILE */ function holdButton(id,key){ const button= document.getElementById(id); button.addEventListener( "pointerdown", e=>{ e.preventDefault(); keys[key]=true; } ); function release(e){ e.preventDefault(); keys[key]=false; } button.addEventListener( "pointerup", release ); button.addEventListener( "pointercancel", release ); button.addEventListener( "pointerleave", release ); } holdButton("left","left"); holdButton("right","right"); holdButton("gas","gas"); holdButton("brake","brake"); /* BACKGROUND */ function drawBackground(){ if(timeMode==="night"){ ctx.fillStyle="#07111f"; ctx.fillRect( 0,0,W,H ); ctx.fillStyle="#fff7c2"; ctx.beginPath(); ctx.arc( W-60, 70, 25, 0, Math.PI*2 ); ctx.fill(); ctx.fillStyle="#101827"; for( let x=0; x70) roadOffset=0; for( let lane=1; lane<3; lane++ ){ const x= roadX+ roadWidth/3*lane; for( let y=roadOffset-70; y=0; i-- ){ const e=enemies[i]; e.y+= speed*e.baseSpeed; e.changeTimer--; if(e.changeTimer<=0){ const currentLane= lanes.reduce( (best,lane)=> Math.abs( W/2+lane-e.targetX )< Math.abs( W/2+best-e.targetX ) ?lane :best, lanes[0] ); const possible= lanes.filter( lane=>lane!==currentLane ); const newLane= possible[ Math.floor( Math.random()*possible.length ) ]; e.targetX= W/2+newLane; e.changeTimer= settings.aiMin+ Math.random()* (settings.aiMax-settings.aiMin); } /* ПЛАВНОЕ ПЕРЕСТРОЕНИЕ */ e.x+= (e.targetX-e.x)* settings.aiSmooth; /* ПРОЙДЕН */ if( e.y>H+120 ){ enemies.splice(i,1); score+=10; continue; } /* СТОЛКНОВЕНИЕ */ const dx= Math.abs( e.x-player.x ); const dy= Math.abs( e.y-player.y ); if( dx< (e.width+player.width)/2-7 && dy< (e.height+player.height)/2-8 ){ endGame(); return; } } } /* PLAYER */ function updatePlayer(){ const steering=3.2; if(keys.left) player.x-=steering; if(keys.right) player.x+=steering; if(keys.gas) speed+=.08; if(keys.brake) speed-=.14; speed-=.015; if(speed<4) speed=4; if(speed>12) speed=12; const minX= W/2-roadWidth/2+30; const maxX= W/2+roadWidth/2-30; if(player.xmaxX) player.x=maxX; } /* LOOP */ function gameLoop(){ if(!running) return; ctx.clearRect( 0,0,W,H ); drawBackground(); drawRoad(); updatePlayer(); updateEnemies(); drawCar( player, "#2563eb" ); for( const enemy of enemies ){ drawCar( enemy, enemy.color ); } document.getElementById( "score" ).textContent=score; document.getElementById( "speed" ).textContent= Math.round(speed*10); requestAnimationFrame( gameLoop ); } /* START */ function startGame(){ if(running) return; running=true; score=0; if(difficulty==="easy") speed=5; if(difficulty==="medium") speed=5.4; if(difficulty==="hard") speed=5.8; enemies=[]; if(difficulty==="easy") enemyTimer=35; if(difficulty==="medium") enemyTimer=25; if(difficulty==="hard") enemyTimer=18; player.x=W/2; player.y=H-220; document.getElementById( "menu" ).style.display="none"; document.getElementById( "gameOver" ).style.display="none"; document.getElementById( "hud" ).style.display="flex"; document.getElementById( "controls" ).style.display="flex"; requestAnimationFrame( gameLoop ); } /* GAME OVER */ function endGame(){ if(!running) return; running=false; if(score>record){ record=score; localStorage.setItem( "racingRecord", record ); document.getElementById( "menuRecord" ).textContent=record; } document.getElementById( "finalScore" ).textContent=score; document.getElementById( "gameOver" ).style.display="flex"; document.getElementById( "hud" ).style.display="none"; document.getElementById( "controls" ).style.display="none"; submitScore(score); } /* BUTTONS */ document.getElementById( "playBtn" ).addEventListener( "click", startGame ); document.getElementById( "restartBtn" ).addEventListener( "click", startGame ); /* SEND SCORE */ async function submitScore(value){ try{ const user= getTelegramUser(); const response= await fetch( API_URL+"/score", { method:"POST", headers:{ "Content-Type": "application/json" }, body:JSON.stringify({ telegramId: String(user.id), name: user.first_name || user.username || "Player", score:value }) } ); if(!response.ok) throw new Error( "Server error" ); await loadLeaderboard(); }catch(error){ console.log( "Score error:", error ); } } /* LEADERBOARD */ async function loadLeaderboard(){ try{ const response= await fetch( API_URL+"/leaderboard" ); const data= await response.json(); const list= document.getElementById( "lbList" ); list.innerHTML=""; if( !Array.isArray(data) || data.length===0 ){ list.innerHTML=`
Пока пусто 🏁
`; return; } data.forEach( (player,index)=>{ const row= document.createElement( "div" ); row.className="lbRow"; row.innerHTML=` ${index+1}. ${escapeHTML(player.name)} ${Number( player.score ).toLocaleString()} `; list.appendChild(row); } ); }catch(error){ console.log( "Leaderboard error:", error ); } } /* SECURITY */ function escapeHTML(text){ return String(text) .replaceAll("&","&") .replaceAll("<","<") .replaceAll(">",">") .replaceAll('"',""") .replaceAll("'","'"); } /* LOAD */ loadLeaderboard();