🎮ArcadeLab

Neon Racer

by BlazeKoala99
798 lines18.3 KB
▶ Play
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Neon Racer</title>

<style>
* { box-sizing: border-box; }

:root {
  --yellow: #ffe45e;
  --cyan: #22d3ee;
  --bg: #080d19;
}

body {
  margin: 0;
  min-height: 100vh;
  text-align: center;
  color: white;
  font-family: system-ui, Arial, sans-serif;
  background:
    radial-gradient(ellipse at top, #17264b, transparent 65%),
    var(--bg);
  user-select: none;
  -webkit-user-select: none;
}

h1 {
  margin: 14px 0 4px;
  color: var(--yellow);
  letter-spacing: 3px;
  text-shadow: 0 0 20px #facc1570;
}

.subtitle {
  margin: 0 0 12px;
  color: #a5b4cc;
  font-size: 12px;
  letter-spacing: 3px;
}

.hud {
  width: min(380px, 94vw);
  margin: 12px auto;
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.stat {
  flex: 1;
  padding: 10px 6px;
  border: 1px solid #ffffff1c;
  border-radius: 12px;
  background: #ffffff0b;
}

.stat small {
  display: block;
  color: #9caecb;
  font-size: 10px;
  letter-spacing: 1px;
}

.stat strong {
  display: block;
  margin-top: 3px;
  font-size: 18px;
  color: var(--yellow);
}

#game {
  position: relative;
  width: min(380px, 94vw);
  height: min(62vh, 620px);
  min-height: 380px;
  margin: auto;
  overflow: hidden;
  background: #292f3d;
  border: 3px solid #536079;
  border-radius: 12px;
  box-shadow: 0 0 40px #0008, 0 0 20px #22d3ee15;
  isolation: isolate;
}

#game::before, #game::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 7px;
  background: linear-gradient(#39e98a, #087c54);
  z-index: 2;
}

#game::before { left: 0; }
#game::after { right: 0; }

.road-line {
  position: absolute;
  width: 2px;
  background: linear-gradient(to bottom, #ffffff22, #ffffff66, #ffffff22);
  top: 0;
  bottom: 0;
  transform: translateX(-50%);
  z-index: 0;
}

.car {
  position: absolute;
  width: 42px;
  height: 66px;
  border: 2px solid #ffffffcc;
  border-radius: 12px 12px 8px 8px;
  box-shadow: 0 5px 12px #0007;
}

.car::before {
  content: "";
  position: absolute;
  top: 12px;
  left: 6px;
  width: 26px;
  height: 20px;
  border-radius: 5px 5px 8px 8px;
  background: linear-gradient(135deg, #dffaff, #38bdf8);
  border: 1px solid #ffffff99;
}

.car::after {
  content: "";
  position: absolute;
  bottom: 5px;
  left: 6px;
  width: 7px;
  height: 5px;
  border-radius: 2px;
  background: #fff8b5;
  box-shadow: 17px 0 #fff8b5;
}

#player {
  position: absolute;
  bottom: 22px;
  background: linear-gradient(90deg, #b91c1c, #ff4c63, #dc2626);
  z-index: 1;
  will-change: left;
}

.coin-item {
  position: absolute;
  width: 24px;
  height: 24px;
  background: var(--yellow);
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 10px var(--yellow);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: bold;
  color: #101323;
}

.enemy {
  background: linear-gradient(90deg, #1d4ed8, #38bdf8, #1e40af);
}

.enemy:nth-of-type(odd) {
  background: linear-gradient(90deg, #6d28d9, #c084fc, #7c3aed);
}

#message {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 24px;
  background: #080d19f0;
  backdrop-filter: blur(7px);
}

#message h2 {
  color: var(--yellow);
  font-size: 26px;
  margin: 0 0 10px;
}

#message p {
  color: #d1d9e8;
  line-height: 1.5;
  font-size: 14px;
  margin-bottom: 16px;
}

.menu-buttons {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 100%;
  max-width: 220px;
}

.shop-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
  width: 100%;
  max-height: 220px;
  overflow-y: auto;
  margin-bottom: 12px;
  padding-right: 4px;
}

.shop-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #ffffff0d;
  border: 1px solid #ffffff20;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 13px;
}

.shop-item button {
  padding: 6px 12px;
  font-size: 12px;
}

button {
  border: 0;
  border-radius: 12px;
  padding: 12px 20px;
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
  transition: transform .12s, filter .12s;
  touch-action: manipulation;
}

button:active {
  transform: scale(.94);
  filter: brightness(1.2);
}

.primary {
  color: #101323;
  background: linear-gradient(135deg, #fff176, #facc15);
  box-shadow: 0 4px 20px #facc1535;
}

.secondary-btn {
  color: white;
  background: #26344d;
  border: 1px solid #ffffff30;
}

#pause {
  color: white;
  background: #26344d;
  border: 1px solid #ffffff30;
  padding: 9px 12px;
}

.controls {
  width: min(380px, 94vw);
  display: flex;
  justify-content: center;
  gap: 18px;
  margin: 14px auto 8px;
}

.controls button {
  flex: 1;
  max-width: 160px;
  height: 62px;
  font-size: 26px;
  color: white;
  background: linear-gradient(145deg, #263852, #18243a);
  border: 1px solid #ffffff25;
  touch-action: none;
}

.hint {
  color: #8e9bb1;
  font-size: 12px;
  margin: 8px 0 18px;
}
</style>
</head>

<body>
<h1>🏎️ NEON RACER</h1>
<p class="subtitle">DODGE • SURVIVE • DOMINATE</p>

<div class="hud">
  <div class="stat">
    <small>SCORE</small>
    <strong id="score">0</strong>
  </div>
  <div class="stat">
    <small>COINS 🪙</small>
    <strong id="coins">0</strong>
  </div>
  <div class="stat">
    <small>BEST 🏆</small>
    <strong id="best">0</strong>
  </div>
  <button id="pause" aria-label="Pause">Ⅱ</button>
</div>

<div id="game">
  <div id="player" class="car"></div>
  <div id="message">
    <h2>🏁 NEON RACER</h2>
    <p>Weiche Autos aus, sammle Coins & schalte Skins frei!</p>
    <div class="menu-buttons">
      <button id="start" class="primary">▶ SPIEL STARTEN</button>
      <button id="shop-btn" class="secondary-btn">🛒 SHOP</button>
    </div>
  </div>
</div>

<div class="controls">
  <button id="left" aria-label="Nach links">◀</button>
  <button id="right" aria-label="Nach rechts">▶</button>
</div>
<p class="hint">Tipp: ← → oder A / D zum Steuern</p>

<script>
const game = document.getElementById("game");
const player = document.getElementById("player");
const message = document.getElementById("message");
const scoreText = document.getElementById("score");
const coinsText = document.getElementById("coins");
const bestText = document.getElementById("best");
const pauseBtn = document.getElementById("pause");

let currentLane = 1;
let targetX = 0;
let x = 0;
let score = 0;
let coins = 0;
let totalCoins = 0;
let speed = 250;
let enemies = [];
let itemCoins = [];
let lines = [];
let running = false;
let paused = false;
let lastTime = 0;
let spawnTimer = 0;
let animationId = 0;
let best = 0;
let keys = { left: false, right: false };

// Skins Definition
const skins = [
  { id: 'classic', name: '🔴 Classic Red', price: 0, css: 'linear-gradient(90deg, #b91c1c, #ff4c63, #dc2626)' },
  { id: 'cyan', name: '💎 Cyan Cyber', price: 50, css: 'linear-gradient(90deg, #0e7490, #22d3ee, #0891b2)' },
  { id: 'gold', name: '👑 Royal Gold', price: 150, css: 'linear-gradient(90deg, #ca8a04, #facc15, #eab308)' },
  { id: 'neon', name: '⚡ Toxic Green', price: 300, css: 'linear-gradient(90deg, #15803d, #4ade80, #16a34a)' }
];

let unlockedSkins = ['classic'];
let currentSkin = 'classic';

// Load Data from LocalStorage
try {
  best = Number(localStorage.getItem("neonRacerBest")) || 0;
  totalCoins = Number(localStorage.getItem("neonRacerCoins")) || 0;
  unlockedSkins = JSON.parse(localStorage.getItem("neonRacerSkins")) || ['classic'];
  currentSkin = localStorage.getItem("neonRacerCurrentSkin") || 'classic';
} catch {}

bestText.textContent = best;
coinsText.textContent = totalCoins;
updatePlayerSkin();

function updatePlayerSkin() {
  const skin = skins.find(s => s.id === currentSkin) || skins[0];
  player.style.background = skin.css;
}

// Road Setup
for (let j = 0; j < 2; j++) {
  const line = document.createElement("div");
  line.className = "road-line";
  game.appendChild(line);
  lines.push({ el: line, lineIndex: j });
}

function getLaneCenterX(lane) {
  const roadWidth = game.clientWidth - 14;
  const laneWidth = roadWidth / 3;
  return 7 + lane * laneWidth + (laneWidth - player.offsetWidth) / 2;
}

function resetRoad() {
  const roadWidth = game.clientWidth - 14;
  const laneWidth = roadWidth / 3;
  lines.forEach((line) => {
    const lineX = 7 + (line.lineIndex + 1) * laneWidth;
    line.el.style.left = lineX + "px";
  });
}

function movePlayer(direction) {
  if (!running || paused) return;
  currentLane = Math.max(0, Math.min(2, currentLane + direction));
  targetX = getLaneCenterX(currentLane);
}

function spawnEntity() {
  const lane = Math.floor(Math.random() * 3);
  const left = getLaneCenterX(lane);

  if (Math.random() < 0.3) {
    const coinEl = document.createElement("div");
    coinEl.className = "coin-item";
    coinEl.textContent = "🪙";
    coinEl.style.left = (left + 9) + "px";
    coinEl.style.top = "-30px";
    game.appendChild(coinEl);
    itemCoins.push({ el: coinEl, y: -30, x: left + 9 });
  } else {
    const enemy = document.createElement("div");
    enemy.className = "car enemy";
    enemy.style.left = left + "px";
    enemy.style.top = "-75px";
    game.appendChild(enemy);
    enemies.push({ el: enemy, y: -75, x: left, passed: false });
  }
}

function hitTest(a, b) {
  return a.left + 6 < b.right - 6 &&
         a.right - 6 > b.left + 6 &&
         a.top + 7 < b.bottom - 7 &&
         a.bottom - 5 > b.top + 5;
}

function updateHud() {
  scoreText.textContent = Math.floor(score);
  coinsText.textContent = totalCoins;
}

function clearEntities() {
  enemies.forEach(e => e.el.remove());
  enemies = [];
  itemCoins.forEach(c => c.el.remove());
  itemCoins = [];
}

function showMainMenu() {
  message.replaceChildren();

  const h = document.createElement("h2");
  h.textContent = "🏁 NEON RACER";
  
  const p = document.createElement("p");
  p.innerHTML = `Coins gesamt: <b>${totalCoins} 🪙</b><br>Weiche dem Verkehr aus & sammle Coins!`;

  const btnContainer = document.createElement("div");
  btnContainer.className = "menu-buttons";

  const startBtn = document.createElement("button");
  startBtn.className = "primary";
  startBtn.textContent = "▶ SPIEL STARTEN";
  startBtn.addEventListener("click", startGame);

  const shopBtn = document.createElement("button");
  shopBtn.className = "secondary-btn";
  shopBtn.textContent = "🛒 SHOP";
  shopBtn.addEventListener("click", showShop);

  btnContainer.append(startBtn, shopBtn);
  message.append(h, p, btnContainer);
  message.style.display = "flex";
}

function showShop() {
  message.replaceChildren();

  const h = document.createElement("h2");
  h.textContent = "🛒 CAR SHOP";

  const p = document.createElement("p");
  p.style.margin = "0 0 8px 0";
  p.innerHTML = `Guthaben: <b>${totalCoins} 🪙</b>`;

  const grid = document.createElement("div");
  grid.className = "shop-grid";

  skins.forEach(skin => {
    const item = document.createElement("div");
    item.className = "shop-item";

    const isUnlocked = unlockedSkins.includes(skin.id);
    const isSelected = currentSkin === skin.id;

    let btnText = "";
    let btnClass = "primary";
    let isDisabled = false;

    if (isSelected) {
      btnText = "AKTIV";
      isDisabled = true;
    } else if (isUnlocked) {
      btnText = "WÄHLEN";
      btnClass = "secondary-btn";
    } else {
      btnText = `${skin.price} 🪙`;
    }

    item.innerHTML = `<span>${skin.name}</span>`;
    const btn = document.createElement("button");
    btn.className = btnClass;
    btn.textContent = btnText;
    if (isDisabled) btn.style.opacity = "0.6";

    btn.addEventListener("click", () => {
      if (isSelected) return;
      if (isUnlocked) {
        currentSkin = skin.id;
        updatePlayerSkin();
        saveData();
        showShop();
      } else if (totalCoins >= skin.price) {
        totalCoins -= skin.price;
        unlockedSkins.push(skin.id);
        currentSkin = skin.id;
        updatePlayerSkin();
        saveData();
        showShop();
      } else {
        alert("Nicht genug Coins!");
      }
    });

    item.appendChild(btn);
    grid.appendChild(item);
  });

  const backBtn = document.createElement("button");
  backBtn.className = "secondary-btn";
  backBtn.textContent = "⬅ ZURÜCK";
  backBtn.style.width = "100%";
  backBtn.addEventListener("click", showMainMenu);

  message.append(h, p, grid, backBtn);
}

function saveData() {
  try {
    localStorage.setItem("neonRacerBest", best);
    localStorage.setItem("neonRacerCoins", totalCoins);
    localStorage.setItem("neonRacerSkins", JSON.stringify(unlockedSkins));
    localStorage.setItem("neonRacerCurrentSkin", currentSkin);
  } catch {}
}

function endGame() {
  running = false;
  paused = false;
  cancelAnimationFrame(animationId);
  pauseBtn.textContent = "Ⅱ";

  totalCoins += coins;
  const finalScore = Math.floor(score);
  if (finalScore > best) best = finalScore;
  saveData();
  updateHud();

  message.replaceChildren();
  const h = document.createElement("h2");
  h.textContent = "💥 GAME OVER";
  const p = document.createElement("p");
  p.innerHTML = `Score: ${finalScore} • Rekord: ${best}<br>Erhaltene Coins: <b>+${coins} 🪙</b>`;
  
  const btnContainer = document.createElement("div");
  btnContainer.className = "menu-buttons";

  const retryBtn = document.createElement("button");
  retryBtn.className = "primary";
  retryBtn.textContent = "↻ NOCHMAL";
  retryBtn.addEventListener("click", startGame);

  const menuBtn = document.createElement("button");
  menuBtn.className = "secondary-btn";
  menuBtn.textContent = "🏠 MENÜ";
  menuBtn.addEventListener("click", showMainMenu);

  btnContainer.append(retryBtn, menuBtn);
  message.append(h, p, btnContainer);
  message.style.display = "flex";
}

function startGame() {
  cancelAnimationFrame(animationId);
  clearEntities();

  score = 0;
  coins = 0;
  speed = 250;
  spawnTimer = 0;
  running = true;
  paused = false;
  keys.left = keys.right = false;

  currentLane = 1;
  x = targetX = getLaneCenterX(currentLane);
  player.style.left = x + "px";

  resetRoad();
  updateHud();
  message.style.display = "none";
  pauseBtn.textContent = "Ⅱ";

  lastTime = performance.now();
  animationId = requestAnimationFrame(loop);
}

function togglePause() {
  if (!running) return;

  paused = !paused;
  pauseBtn.textContent = paused ? "▶" : "Ⅱ";

  if (paused) {
    cancelAnimationFrame(animationId);
    message.replaceChildren();
    const h = document.createElement("h2");
    h.textContent = "⏸ PAUSE";
    
    const btnContainer = document.createElement("div");
    btnContainer.className = "menu-buttons";

    const resumeBtn = document.createElement("button");
    resumeBtn.className = "primary";
    resumeBtn.textContent = "▶ WEITER";
    resumeBtn.addEventListener("click", () => {
      message.style.display = "none";
      paused = false;
      pauseBtn.textContent = "Ⅱ";
      lastTime = performance.now();
      animationId = requestAnimationFrame(loop);
    });

    const menuBtn = document.createElement("button");
    menuBtn.className, "secondary-btn";
    menuBtn.textContent = "🏠 MENÜ";
    menuBtn.addEventListener("click", showMainMenu);

    btnContainer.append(resumeBtn, menuBtn);
    message.append(h, btnContainer);
    message.style.display = "flex";
  }
}

let keyCooldown = false;
function loop(time) {
  if (!running || paused) return;

  const dt = Math.min((time - lastTime) / 1000, 0.04);
  lastTime = time;

  // Score kontinuierlich basierend auf der aktuellen Geschwindigkeit erhöhen (wie in Subway Surfers)
  score += speed * dt * 0.5;
  updateHud();

  if ((keys.left || keys.right) && !keyCooldown) {
    if (keys.left) movePlayer(-1);
    if (keys.right) movePlayer(1);
    keyCooldown = true;
    setTimeout(() => { keyCooldown = false; }, 150);
  }

  targetX = getLaneCenterX(currentLane);
  x += (targetX - x) * Math.min(1, 16 * dt);
  player.style.left = x + "px";

  spawnTimer += dt;
  const spawnInterval = Math.max(0.45, 1.0 - (score * 0.0001));

  if (spawnTimer >= spawnInterval) {
    spawnEntity();
    spawnTimer = 0;
  }

  const playerRect = player.getBoundingClientRect();

  // Gegner aktualisieren & Kollision prüfen
  for (let i = enemies.length - 1; i >= 0; i--) {
    const enemy = enemies[i];
    enemy.y += speed * dt;
    enemy.el.style.top = enemy.y + "px";

    if (hitTest(playerRect, enemy.el.getBoundingClientRect())) {
      endGame();
      return;
    }

    if (enemy.y > game.clientHeight) {
      enemy.el.remove();
      enemies.splice(i, 1);

      if (!enemy.passed) {
        speed = Math.min(650, 250 + score * 0.05);
      }
    }
  }

  // Coins aktualisieren & Einsammeln prüfen
  for (let i = itemCoins.length - 1; i >= 0; i--) {
    const c = itemCoins[i];
    c.y += speed * dt;
    c.el.style.top = c.y + "px";

    if (hitTest(playerRect, c.el.getBoundingClientRect())) {
      c.el.remove();
      itemCoins.splice(i, 1);
      coins++;
      updateHud();
      continue;
    }

    if (c.y > game.clientHeight) {
      c.el.remove();
      itemCoins.splice(i, 1);
    }
  }

  animationId = requestAnimationFrame(loop);
}

document.getElementById("start").addEventListener("click", startGame);
document.getElementById("shop-btn").addEventListener("click", showShop);
pauseBtn.addEventListener("click", togglePause);

document.addEventListener("keydown", e => {
  const k = e.key.toLowerCase();
  if (["arrowleft", "arrowright", " "].includes(k)) e.preventDefault();

  if (k === "arrowleft" || k === "a") keys.left = true;
  if (k === "arrowright" || k === "d") keys.right = true;

  if (k === " " && !e.repeat) {
    if (!running && message.style.display !== "none") {
      startGame();
    } else {
      togglePause();
    }
  }
});

document.addEventListener("keyup", e => {
  const k = e.key.toLowerCase();
  if (k === "arrowleft" || k === "a") keys.left = false;
  if (k === "arrowright" || k === "d") keys.right = false;
});

window.addEventListener("blur", () => {
  keys.left = keys.right = false;
  keyCooldown = false;
});

function bindControl(id, direction) {
  const btn = document.getElementById(id);
  btn.addEventListener("pointerdown", e => {
    e.preventDefault();
    btn.setPointerCapture(e.pointerId);
    movePlayer(direction);
  });
}

bindControl("left", -1);
bindControl("right", 1);

window.addEventListener("resize", () => {
  x = targetX = getLaneCenterX(currentLane);
  player.style.left = x + "px";
  resetRoad();
});

resetRoad();
showMainMenu();
</script>
</body>
</html>

Game Source: Neon Racer

Creator: BlazeKoala99

Libraries: none

Complexity: complex (798 lines, 18.3 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-racer-blazekoala99" to link back to the original. Then publish at arcadelab.ai/publish.