🎮ArcadeLab

Neon Racer X

by SolarKnight80
979 lines25.3 KB
▶ Play

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neon Racer X</title>
<style>
:root {
  --cyan: #00f5ff;
  --pink: #ff2bd6;
  --purple: #8b5cf6;
  --bg: #070817;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: white;
  font-family: Arial, sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 12px;
}
button {
  border: 1px solid var(--cyan);
  background: #10152b;
  color: white;
  padding: 11px 15px;
  border-radius: 10px;
  font-weight: bold;
  cursor: pointer;
  box-shadow: 0 0 10px #00f5ff33;
}
button:hover { background: #1b2945; }
button:disabled { opacity: .4; cursor: not-allowed; }
#app {
  width: 100%;
  max-width: 440px;
  text-align: center;
}
h1 {
  color: var(--cyan);
  text-shadow: 0 0 15px var(--cyan);
  margin: 4px 0 12px;
  font-size: clamp(25px, 7vw, 36px);
}
#topbar {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
  font-size: 14px;
}
.stat {
  flex: 1;
  background: #11152a;
  padding: 10px 5px;
  border: 1px solid #282e52;
  border-radius: 10px;
}
#game {
  width: 100%;
  height: min(66vh, 610px);
  min-height: 380px;
  max-height: 610px;
  position: relative;
  overflow: hidden;
  background: #090d19;
  border: 2px solid var(--cyan);
  border-radius: 14px;
  box-shadow: 0 0 20px #00f5ff30;
  touch-action: pan-y;
}
#road {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg,
    #101424 0%, #101424 2%,
    #1a2032 2%, #1a2032 98%,
    #101424 98%, #101424 100%);
}
.lane-line {
  position: absolute;
  top: -100%;
  height: 200%;
  width: 3px;
  background: repeating-linear-gradient(
    to bottom, #65708c 0 28px, transparent 28px 55px
  );
  opacity: .55;
}
#line1 { left: 33.333%; }
#line2 { left: 66.666%; }
.car {
  position: absolute;
  width: 48px;
  height: 76px;
  border-radius: 12px 12px 8px 8px;
  border: 2px solid white;
  box-shadow: 0 0 13px currentColor;
  transform: translateX(-50%);
  z-index: 4;
}
.car::before {
  content: "";
  position: absolute;
  width: 27px;
  height: 25px;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  background: #b9faff;
  border-radius: 7px 7px 3px 3px;
  border: 2px solid #e7ffff;
}
.car::after {
  content: "";
  position: absolute;
  bottom: 8px;
  left: 8px;
  right: 8px;
  height: 8px;
  border-radius: 3px;
  background: #ff365e;
  box-shadow: 0 0 8px #ff365e;
}
#player {
  bottom: 30px;
  left: 50%;
  background: #00bcd4;
  color: var(--cyan);
}
.enemy {
  background: #b72c75;
  color: var(--pink);
}
.enemy::before { background: #ff9ddf; }
.pickup {
  position: absolute;
  z-index: 5;
  transform: translate(-50%, -50%);
  font-size: 27px;
  filter: drop-shadow(0 0 8px currentColor);
}
#effects {
  position: absolute;
  z-index: 7;
  top: 8px;
  left: 8px;
  right: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  pointer-events: none;
}
.effect {
  padding: 5px 8px;
  background: #11182ddd;
  border: 1px solid var(--cyan);
  border-radius: 8px;
  font-size: 12px;
}
#shieldRing {
  position: absolute;
  width: 64px;
  height: 91px;
  border: 3px solid #00f5ff;
  box-shadow: 0 0 16px #00f5ff;
  border-radius: 18px;
  bottom: 22px;
  left: 50%;
  transform: translateX(-50%);
  display: none;
  z-index: 3;
  pointer-events: none;
}
#overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  background: #050716eF;
}
.panel {
  width: 100%;
  max-height: 100%;
  overflow-y: auto;
  background: #10152b;
  border: 1px solid var(--cyan);
  border-radius: 15px;
  padding: 18px;
  box-shadow: 0 0 22px #00f5ff22;
}
.panel h2 { color: var(--cyan); margin: 0 0 12px; }
.panel p { line-height: 1.45; }
.hidden { display: none !important; }
.menu-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 12px;
}
.menu-buttons button { width: 100%; }
.wide { grid-column: 1 / -1; }
#controls {
  display: flex;
  gap: 8px;
  margin-top: 9px;
}
#controls button { flex: 1; font-size: 17px; }
#message {
  min-height: 20px;
  margin: 8px 0 0;
  font-size: 13px;
  color: #b9c8e8;
}
.upgrade-card {
  border: 1px solid #343d62;
  border-radius: 10px;
  padding: 10px;
  margin: 9px 0;
  text-align: left;
  background: #0a1022;
}
.upgrade-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.upgrade-card p {
  color: #b9c8e8;
  font-size: 13px;
  margin: 7px 0;
}
.level-bar {
  display: flex;
  gap: 4px;
  margin: 8px 0;
}
.level-bar span {
  height: 7px;
  flex: 1;
  background: #272e49;
  border-radius: 5px;
}
.level-bar span.on {
  background: var(--cyan);
  box-shadow: 0 0 7px #00f5ff;
}
.small { font-size: 12px; color: #b9c8e8; }
#footer { margin-top: 8px; color: #9da9c8; font-size: 12px; }
</style>
</head>
<body>
<div id="app">
  <h1>NEON RACER X</h1>

  <div id="topbar">
    <div class="stat">🏆 Score<br><strong id="score">0</strong></div>
    <div class="stat">🪙 Münzen<br><strong id="coins">0</strong></div>
    <div class="stat">⚡ Bestwert<br><strong id="best">0</strong></div>
  </div>

  <div id="game">
    <div id="road">
      <div class="lane-line" id="line1"></div>
      <div class="lane-line" id="line2"></div>
    </div>

    <div id="effects"></div>
    <div id="shieldRing"></div>
    <div class="car" id="player"></div>

    <div id="overlay">
      <div class="panel" id="mainMenu">
        <h2>🏁 Neon Racer X</h2>
        <p>Weiche Hindernissen aus, sammle Münzen und verbessere deine Power-ups mit Upgrades.</p>
        <p>← → oder A/D zum Bewegen. Auf dem Handy kannst du wischen oder die Buttons benutzen.</p>
        <div class="menu-buttons">
          <button class="wide" id="startBtn">▶ Spielen</button>
          <button id="upgradeBtn">⬆ Upgrades</button>
          <button id="shopBtn">🎨 Skins</button>
          <button class="wide" id="questBtn">📅 Tagesaufgaben</button>
        </div>
      </div>

      <div class="panel hidden" id="upgradeMenu">
        <h2>⬆ Upgrade-Shop</h2>
        <p>Deine Münzen: 🪙 <strong id="upgradeCoins">0</strong></p>
        <p class="small">Jede gekaufte Stufe verlängert Schild und Magnet. Die Stufen werden dauerhaft gespeichert.</p>
        <div id="upgradeList"></div>
        <button class="wide" id="upgradeBack">↩ Zurück</button>
      </div>

      <div class="panel hidden" id="shopMenu">
        <h2>🎨 Skin-Shop</h2>
        <p>Münzen: 🪙 <strong id="shopCoins">0</strong></p>
        <div id="skinList"></div>
        <button class="wide" id="shopBack">↩ Zurück</button>
      </div>

      <div class="panel hidden" id="questMenu">
        <h2>📅 Tagesaufgaben</h2>
        <div id="questList"></div>
        <button class="wide" id="questBack">↩ Zurück</button>
      </div>

      <div class="panel hidden" id="pauseMenu">
        <h2>⏸ Pause</h2>
        <button class="wide" id="resumeBtn">Weiter spielen</button>
        <button class="wide" id="pauseHomeBtn" style="margin-top:8px">Hauptmenü</button>
      </div>

      <div class="panel hidden" id="overMenu">
        <h2>💥 Game Over</h2>
        <p>Dein Score: <strong id="finalScore">0</strong></p>
        <p>Gesammelte Münzen: <strong id="finalCoins">0</strong></p>
        <button class="wide" id="againBtn">🔄 Nochmal spielen</button>
        <button class="wide" id="overHomeBtn" style="margin-top:8px">Hauptmenü</button>
      </div>
    </div>
  </div>

  <div id="controls">
    <button id="leftBtn">◀</button>
    <button id="pauseBtn">Ⅱ Pause</button>
    <button id="rightBtn">▶</button>
  </div>
  <div id="message">Sammle Münzen für deine Upgrades!</div>
  <div id="footer">Schild: 🛡️ · Magnet: 🧲 · Münze: 🪙</div>
</div>

<script>
"use strict";

const $ = id => document.getElementById(id);
const game = $("game");
const player = $("player");
const overlay = $("overlay");

const UPGRADE_PRICES = [200, 500, 1000, 1500, 1500];
const UPGRADE_NAMES = [
  "Stufe 1 – Power-up Plus",
  "Stufe 2 – Power-up Pro",
  "Stufe 3 – Power-up Ultra",
  "Stufe 4 – Power-up Elite",
  "Stufe 5 – Power-up Max"
];

const SKINS = [
  {name:"Neon Cyan", color:"#00bcd4", price:0},
  {name:"Hot Pink", color:"#ff2bd6", price:150},
  {name:"Lime Flash", color:"#7dff4f", price:300},
  {name:"Gold Rush", color:"#ffbf00", price:500}
];

function loadNumber(key, fallback=0) {
  try {
    const n = Number(localStorage.getItem(key));
    return Number.isFinite(n) && n >= 0 ? n : fallback;
  } catch { return fallback; }
}
function loadJSON(key, fallback) {
  try {
    const value = JSON.parse(localStorage.getItem(key));
    return value ?? fallback;
  } catch { return fallback; }
}
function save(key, value) {
  try { localStorage.setItem(key, String(value)); } catch {}
}

let coins = loadNumber("nr_coins", 0);
let best = loadNumber("nr_best", 0);
let upgradeLevel = Math.min(5, Math.floor(loadNumber("nr_upgradeLevel", 0)));
let ownedSkins = loadJSON("nr_ownedSkins", [0]);
let selectedSkin = Math.min(SKINS.length-1, loadNumber("nr_selectedSkin", 0));
if (!Array.isArray(ownedSkins)) ownedSkins = [0];
if (!ownedSkins.includes(0)) ownedSkins.push(0);

let running = false;
let paused = false;
let gameOver = false;
let score = 0;
let runCoins = 0;
let elapsed = 0;
let lastTime = 0;
let spawnTimer = 0;
let coinTimer = 0;
let powerTimer = 0;
let lane = 1;
let enemies = [];
let pickups = [];
let roadOffset = 0;
let shieldTime = 0;
let magnetTime = 0;
let lastQuestSecond = 0;
let animationId = 0;

const daily = {
  date: new Date().toDateString(),
  coins: 0,
  score: 0,
  games: 0
};

const savedDaily = loadJSON("nr_daily", null);
if (savedDaily && savedDaily.date === daily.date) {
  daily.coins = savedDaily.coins || 0;
  daily.score = savedDaily.score || 0;
  daily.games = savedDaily.games || 0;
}
function saveDaily() { save("nr_daily", JSON.stringify(daily)); }

const quests = [
  {id:"coins", name:"Sammle 30 Münzen", target:30, reward:75},
  {id:"score", name:"Erreiche 1000 Punkte", target:1000, reward:100},
  {id:"games", name:"Spiele 3 Runden", target:3, reward:60}
];
let claimed = loadJSON("nr_claimed", {});
if (!claimed || typeof claimed !== "object") claimed = {};

function getQuestProgress(q) {
  if (q.id === "coins") return daily.coins;
  if (q.id === "score") return daily.score;
  return daily.games;
}
function saveAll() {
  save("nr_coins", coins);
  save("nr_best", best);
  save("nr_upgradeLevel", upgradeLevel);
  save("nr_ownedSkins", JSON.stringify(ownedSkins));
  save("nr_selectedSkin", selectedSkin);
  save("nr_claimed", JSON.stringify(claimed));
  saveDaily();
}
function setMessage(text) { $("message").textContent = text; }
function updateHUD() {
  $("score").textContent = Math.floor(score);
  $("coins").textContent = coins;
  $("best").textContent = best;
}
function setPlayerSkin() {
  player.style.background = SKINS[selectedSkin].color;
  player.style.color = SKINS[selectedSkin].color;
}
function currentShieldDuration() {
  return 7 + upgradeLevel * 3;
}
function currentMagnetDuration() {
  return 8 + upgradeLevel * 3;
}
function laneX(index) {
  return (index + 0.5) * game.clientWidth / 3;
}
function playerY() {
  return game.clientHeight - 30 - player.offsetHeight / 2;
}
function setLane(index) {
  lane = Math.max(0, Math.min(2, index));
  player.style.left = laneX(lane) + "px";
  $("shieldRing").style.left = laneX(lane) + "px";
}
function showPanel(panelId) {
  overlay.classList.remove("hidden");
  for (const el of overlay.querySelectorAll(".panel")) el.classList.add("hidden");
  $(panelId).classList.remove("hidden");
}
function showMainMenu() {
  running = false;
  paused = false;
  gameOver = false;
  showPanel("mainMenu");
  $("pauseBtn").textContent = "Ⅱ Pause";
  setMessage("Sammle Münzen für deine Upgrades!");
  cancelAnimationFrame(animationId);
}
function makeElement(className, text) {
  const el = document.createElement("div");
  el.className = className;
  if (text) el.textContent = text;
  game.appendChild(el);
  return el;
}

/* ---------------- UPGRADE-SHOP ---------------- */

function renderUpgrades() {
  $("upgradeCoins").textContent = coins;
  const list = $("upgradeList");
  list.innerHTML = "";

  for (let i = 0; i < 5; i++) {
    const level = i + 1;
    const bought = upgradeLevel >= level;
    const next = upgradeLevel + 1 === level;
    const price = UPGRADE_PRICES[i];

    const card = document.createElement("div");
    card.className = "upgrade-card";

    const head = document.createElement("div");
    head.className = "upgrade-head";

    const title = document.createElement("strong");
    title.textContent = UPGRADE_NAMES[i];

    const status = document.createElement("span");
    status.className = "small";
    status.textContent = bought ? "✓ Gekauft" : (next ? price + " 🪙" : "Gesperrt");

    head.append(title, status);
    card.appendChild(head);

    const bar = document.createElement("div");
    bar.className = "level-bar";
    for (let j = 0; j < 5; j++) {
      const segment = document.createElement("span");
      if (j < level && upgradeLevel >= j + 1) segment.classList.add("on");
      bar.appendChild(segment);
    }
    card.appendChild(bar);

    const desc = document.createElement("p");
    desc.textContent = "Schild: " + (7 + level * 3) +
      " Sek. · Magnet: " + (8 + level * 3) + " Sek.";
    card.appendChild(desc);

    const button = document.createElement("button");
    button.style.width = "100%";

    if (bought) {
      button.textContent = "Bereits gekauft";
      button.disabled = true;
    } else if (!next) {
      button.textContent = "Erst Stufe " + (level - 1) + " kaufen";
      button.disabled = true;
    } else {
      button.textContent = "⬆ Kaufen – " + price + " Münzen";
      button.disabled = coins < price;
      button.addEventListener("click", () => buyUpgrade(level));
    }
    card.appendChild(button);
    list.appendChild(card);
  }

  if (upgradeLevel >= 5) {
    const max = document.createElement("p");
    max.style.color = "#00f5ff";
    max.textContent = "⭐ Maximale Upgrade-Stufe erreicht!";
    list.appendChild(max);
  }
}

function buyUpgrade(level) {
  if (level !== upgradeLevel + 1 || level < 1 || level > 5) return;
  const price = UPGRADE_PRICES[level - 1];

  if (coins < price) {
    setMessage("Du hast nicht genug Münzen!");
    return;
  }

  coins -= price;
  upgradeLevel = level;
  saveAll();
  updateHUD();
  renderUpgrades();
  setMessage("Upgrade " + level + " gekauft! Schild und Magnet halten länger.");
}

/* ---------------- SKIN-SHOP ---------------- */

function renderSkins() {
  $("shopCoins").textContent = coins;
  const list = $("skinList");
  list.innerHTML = "";

  SKINS.forEach((skin, i) => {
    const card = document.createElement("div");
    card.className = "upgrade-card";

    const head = document.createElement("div");
    head.className = "upgrade-head";

    const title = document.createElement("strong");
    title.textContent = "🚗 " + skin.name;
    title.style.color = skin.color;

    const status = document.createElement("span");
    status.className = "small";
    status.textContent = ownedSkins.includes(i) ? (selectedSkin === i ? "Ausgewählt" : "Besitzt du") : skin.price + " 🪙";

    head.append(title, status);
    card.appendChild(head);

    const button = document.createElement("button");
    button.style.width = "100%";

    if (ownedSkins.includes(i)) {
      button.textContent = selectedSkin === i ? "Ausgewählt" : "Skin auswählen";
      button.disabled = selectedSkin === i;
      button.addEventListener("click", () => {
        selectedSkin = i;
        saveAll();
        setPlayerSkin();
        renderSkins();
      });
    } else {
      button.textContent = "Kaufen – " + skin.price + " Münzen";
      button.disabled = coins < skin.price;
      button.addEventListener("click", () => {
        if (coins < skin.price) return;
        coins -= skin.price;
        ownedSkins.push(i);
        selectedSkin = i;
        saveAll();
        updateHUD();
        setPlayerSkin();
        renderSkins();
        setMessage("Neuer Skin freigeschaltet!");
      });
    }
    card.appendChild(button);
    list.appendChild(card);
  });
}

/* ---------------- TAGESAUFGABEN ---------------- */

function renderQuests() {
  const list = $("questList");
  list.innerHTML = "";

  quests.forEach(q => {
    const progress = Math.min(q.target, getQuestProgress(q));
    const done = progress >= q.target;
    const isClaimed = !!claimed[q.id];

    const card = document.createElement("div");
    card.className = "upgrade-card";

    const title = document.createElement("strong");
    title.textContent = q.name;
    card.appendChild(title);

    const desc = document.createElement("p");
    desc.textContent = progress + " / " + q.target + " · Belohnung: " + q.reward + " Münzen";
    card.appendChild(desc);

    const button = document.createElement("button");
    button.style.width = "100%";
    button.textContent = isClaimed ? "Belohnung abgeholt" : done ? "Belohnung abholen" : "Noch nicht geschafft";
    button.disabled = !done || isClaimed;
    button.addEventListener("click", () => {
      if (!done || claimed[q.id]) return;
      claimed[q.id] = true;
      coins += q.reward;
      saveAll();
      updateHUD();
      renderQuests();
      setMessage("Aufgabe abgeschlossen! +" + q.reward + " Münzen");
    });
    card.appendChild(button);
    list.appendChild(card);
  });
}

function addCoins(amount) {
  coins += amount;
  runCoins += amount;
  daily.coins += amount;
  saveAll();
  updateHUD();
}

/* ---------------- SPIEL ---------------- */

function clearObjects() {
  enemies.forEach(e => e.el.remove());
  pickups.forEach(p => p.el.remove());
  enemies = [];
  pickups = [];
}
function resetGame() {
  clearObjects();
  score = 0;
  runCoins = 0;
  elapsed = 0;
  spawnTimer = 0;
  coinTimer = 0;
  powerTimer = 0;
  roadOffset = 0;
  shieldTime = 0;
  magnetTime = 0;
  lastQuestSecond = 0;
  lane = 1;
  setLane(lane);
  updateHUD();
  $("effects").innerHTML = "";
  $("shieldRing").style.display = "none";
  setMessage("Viel Glück!");
}
function startGame() {
  cancelAnimationFrame(animationId);
  resetGame();
  running = true;
  paused = false;
  gameOver = false;
  overlay.classList.add("hidden");
  lastTime = performance.now();
  animationId = requestAnimationFrame(loop);
}
function spawnEnemy() {
  const laneIndex = Math.floor(Math.random() * 3);
  const el = makeElement("car enemy");
  el.style.left = laneX(laneIndex) + "px";
  el.style.top = "-90px";
  enemies.push({el, lane:laneIndex, y:-90, passed:false});
}
function spawnPickup() {
  const laneIndex = Math.floor(Math.random() * 3);
  const roll = Math.random();
  let type = "coin";
  let symbol = "🪙";

  if (roll > .91) {
    type = "shield";
    symbol = "🛡️";
  } else if (roll > .82) {
    type = "magnet";
    symbol = "🧲";
  }

  const el = makeElement("pickup", symbol);
  el.style.left = laneX(laneIndex) + "px";
  el.style.top = "-25px";
  pickups.push({el, lane:laneIndex, y:-25, type});
}
function activatePickup(type) {
  if (type === "shield") {
    shieldTime = currentShieldDuration();
    setMessage("🛡️ Schild aktiviert: " + Math.ceil(shieldTime) + " Sekunden!");
  } else if (type === "magnet") {
    magnetTime = currentMagnetDuration();
    setMessage("🧲 Magnet aktiviert: " + Math.ceil(magnetTime) + " Sekunden!");
  }
}
function updateEffects() {
  const effects = $("effects");
  effects.innerHTML = "";
  if (shieldTime > 0) {
    const el = document.createElement("span");
    el.className = "effect";
    el.textContent = "🛡️ Schild " + Math.ceil(shieldTime) + "s";
    effects.appendChild(el);
  }
  if (magnetTime > 0) {
    const el = document.createElement("span");
    el.className = "effect";
    el.textContent = "🧲 Magnet " + Math.ceil(magnetTime) + "s";
    effects.appendChild(el);
  }
  $("shieldRing").style.display = shieldTime > 0 ? "block" : "none";
}
function endGame() {
  if (gameOver) return;
  gameOver = true;
  running = false;
  cancelAnimationFrame(animationId);

  best = Math.max(best, Math.floor(score));
  daily.score = Math.max(daily.score, Math.floor(score));
  daily.games += 1;
  saveAll();
  updateHUD();
  $("finalScore").textContent = Math.floor(score);
  $("finalCoins").textContent = runCoins;
  renderQuests();
  showPanel("overMenu");
}
function moveLane(direction) {
  if (!running || paused) return;
  setLane(lane + direction);
}
function togglePause() {
  if (!running && !paused) return;
  if (paused) {
    paused = false;
    overlay.classList.add("hidden");
    lastTime = performance.now();
    animationId = requestAnimationFrame(loop);
  } else {
    paused = true;
    showPanel("pauseMenu");
    $("pauseBtn").textContent = "▶ Weiter";
    cancelAnimationFrame(animationId);
  }
}
function collision(a, b) {
  const ar = a.getBoundingClientRect();
  const br = b.getBoundingClientRect();
  return ar.left < br.right - 7 &&
    ar.right > br.left + 7 &&
    ar.top < br.bottom - 8 &&
    ar.bottom > br.top + 8;
}

function loop(now) {
  if (!running || paused || gameOver) return;
  const dt = Math.min((now - lastTime) / 1000, .05);
  lastTime = now;
  elapsed += dt;

  /* Die Schwierigkeit steigt langsam und ist begrenzt. */
  const speed = Math.min(520, 230 + score * .018);
  const spawnInterval = Math.max(.62, 1.05 - score * .000025);

  roadOffset = (roadOffset + speed * dt) % 55;
  $("line1").style.transform = "translateY(" + roadOffset + "px)";
  $("line2").style.transform = "translateY(" + roadOffset + "px)";

  score += dt * 10;
  shieldTime = Math.max(0, shieldTime - dt);
  magnetTime = Math.max(0, magnetTime - dt);

  spawnTimer += dt;
  coinTimer += dt;
  powerTimer += dt;

  if (spawnTimer >= spawnInterval) {
    spawnTimer = 0;
    spawnEnemy();
    if (score > 1800 && Math.random() < .08) spawnEnemy();
  }
  if (coinTimer >= .85) {
    coinTimer = 0;
    spawnPickup();
  }
  if (powerTimer >= 5.5) {
    powerTimer = 0;
    if (Math.random() < .22) spawnPickup();
  }

  const pRect = player.getBoundingClientRect();
  const playerCenterY = pRect.top + pRect.height / 2;

  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 (collision(player, enemy.el)) {
      if (shieldTime > 0) {
        shieldTime = 0;
        enemy.el.remove();
        enemies.splice(i, 1);
        setMessage("Schild hat den Zusammenstoß abgefangen!");
        continue;
      }
      endGame();
      return;
    }

    if (enemy.y > game.clientHeight + 100) {
      enemy.el.remove();
      enemies.splice(i, 1);
      score += 5;
    }
  }

  for (let i = pickups.length - 1; i >= 0; i--) {
    const pickup = pickups[i];
    pickup.y += speed * dt;
    pickup.el.style.top = pickup.y + "px";

    if (magnetTime > 0 && pickup.type === "coin") {
      const targetX = laneX(lane);
      const currentX = parseFloat(pickup.el.style.left);
      const dx = targetX - currentX;
      pickup.el.style.left = (currentX + dx * Math.min(1, dt * 5)) + "px";
      pickup.lane = lane;
    }

    if (collision(player, pickup.el)) {
      if (pickup.type === "coin") {
        addCoins(1);
        score += 15;
      } else {
        activatePickup(pickup.type);
      }
      pickup.el.remove();
      pickups.splice(i, 1);
      continue;
    }

    if (pickup.y > game.clientHeight + 40) {
      pickup.el.remove();
      pickups.splice(i, 1);
    }
  }

  const wholeSecond = Math.floor(elapsed);
  if (wholeSecond > lastQuestSecond) {
    lastQuestSecond = wholeSecond;
  }

  updateEffects();
  updateHUD();
  save("nr_best", best);
  animationId = requestAnimationFrame(loop);
}

/* ---------------- BEDIENUNG ---------------- */

$("startBtn").addEventListener("click", startGame);
$("againBtn").addEventListener("click", startGame);
$("upgradeBtn").addEventListener("click", () => {
  renderUpgrades();
  showPanel("upgradeMenu");
});
$("upgradeBack").addEventListener("click", () => showPanel("mainMenu"));
$("shopBtn").addEventListener("click", () => {
  renderSkins();
  showPanel("shopMenu");
});
$("shopBack").addEventListener("click", () => showPanel("mainMenu"));
$("questBtn").addEventListener("click", () => {
  renderQuests();
  showPanel("questMenu");
});
$("questBack").addEventListener("click", () => showPanel("mainMenu"));
$("pauseBtn").addEventListener("click", togglePause);
$("resumeBtn").addEventListener("click", togglePause);
$("pauseHomeBtn").addEventListener("click", showMainMenu);
$("overHomeBtn").addEventListener("click", showMainMenu);
$("leftBtn").addEventListener("click", () => moveLane(-1));
$("rightBtn").addEventListener("click", () => moveLane(1));

document.addEventListener("keydown", event => {
  if (event.key === "ArrowLeft" || event.key.toLowerCase() === "a") moveLane(-1);
  if (event.key === "ArrowRight" || event.key.toLowerCase() === "d") moveLane(1);
  if (event.key === "Escape" || event.key.toLowerCase() === "p") togglePause();
  if (event.key === " " && !running && !paused) startGame();
});

let touchStartX = null;
game.addEventListener("touchstart", event => {
  touchStartX = event.changedTouches[0].clientX;
}, {passive:true});
game.addEventListener("touchend", event => {
  if (touchStartX === null) return;
  const diff = event.changedTouches[0].clientX - touchStartX;
  if (Math.abs(diff) > 25) moveLane(diff > 0 ? 1 : -1);
  touchStartX = null;
}, {passive:true});

window.addEventListener("resize", () => setLane(lane));

/* ---------------- START ---------------- */

setPlayerSkin();
setLane(1);
updateHUD();
renderUpgrades();
</script>
</body>
</html>

Game Source: Neon Racer X

Creator: SolarKnight80

Libraries: none

Complexity: complex (979 lines, 25.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-x-solarknight80" to link back to the original. Then publish at arcadelab.ai/publish.