🎮ArcadeLab

Neon Racer X

by BlazeKoala99
985 lines30.0 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: #03040c;
}
*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  background: radial-gradient(circle at 50% 20%, #0d122d 0%, var(--bg) 100%);
  color: #fff;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 12px;
}
button {
  border: 1px solid rgba(0, 245, 255, 0.4);
  background: linear-gradient(135deg, #101633 0%, #080c1f 100%);
  color: #fff;
  padding: 12px 18px;
  border-radius: 12px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 4px 15px rgba(0, 245, 255, 0.1);
  transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
}
button:hover {
  background: linear-gradient(135deg, #17234a 0%, #0d132b 100%);
  border-color: var(--cyan);
  box-shadow: 0 0 20px rgba(0, 245, 255, 0.35);
  transform: translateY(-2px);
}
button:active { transform: translateY(1px); }
button:disabled { opacity: .4; cursor: not-allowed; box-shadow: none; transform: none; }

#app {
  width: 100%;
  max-width: 440px;
  text-align: center;
}

/* --- FAKE LOADER SCREEN (GRÖßER GEMACHT) --- */
#loaderOverlay {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #03040c;
  padding: 32px;
  transition: opacity 0.4s ease;
}
#loaderOverlay.fade-out {
  opacity: 0;
  pointer-events: none;
}
.loader-content {
  width: 100%;
  max-width: 380px; /* Breiter gemacht */
  text-align: center;
}
.loader-title {
  color: var(--cyan);
  font-size: 28px; /* Größere Schrift */
  font-weight: 800;
  letter-spacing: 4px;
  margin-bottom: 12px;
  text-shadow: 0 0 20px rgba(0,245,255,0.7);
}
.loader-subtitle {
  color: #94a3b8;
  font-size: 15px; /* Größere Untertitel-Schrift */
  margin-bottom: 30px;
  letter-spacing: 2px;
}
.loader-bar-bg {
  width: 100%;
  height: 16px; /* Dickerer Ladebalken */
  background: #0d122d;
  border: 2px solid rgba(0, 245, 255, 0.4); /* Kräftigerer Rand */
  border-radius: 8px;
  overflow: hidden;
  position: relative;
  box-shadow: inset 0 0 12px rgba(0,0,0,0.8), 0 0 15px rgba(0, 245, 255, 0.2);
}
.loader-bar-fill {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, var(--cyan), var(--pink));
  box-shadow: 0 0 20px var(--cyan);
  border-radius: 6px;
  transition: width 0.05s linear;
}
.loader-percentage {
  margin-top: 16px;
  font-size: 22px; /* Größere Prozentanzeige */
  font-weight: 700;
  color: #fff;
  letter-spacing: 2px;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 0 10px rgba(255,255,255,0.5);
}

/* --- HAUPTMENÜ DESIGN --- */
#mainMenu {
  background: linear-gradient(180deg, rgba(16, 22, 48, 0.95) 0%, rgba(8, 12, 28, 0.98) 100%);
  border: 2px solid rgba(0, 245, 255, 0.5);
  box-shadow: 0 0 40px rgba(0, 245, 255, 0.2), inset 0 0 20px rgba(0, 245, 255, 0.05);
  position: relative;
  overflow: hidden;
}
#mainMenu::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 3px;
  background: linear-gradient(90deg, transparent, var(--cyan), var(--pink), transparent);
}

.logo-container { padding: 10px 0 5px; }
.logo-badge {
  display: inline-block;
  padding: 4px 12px;
  background: rgba(0, 245, 255, 0.1);
  border: 1px solid rgba(0, 245, 255, 0.3);
  border-radius: 20px;
  font-size: 11px;
  letter-spacing: 3px;
  color: var(--cyan);
  text-transform: uppercase;
  margin-bottom: 8px;
  box-shadow: 0 0 10px rgba(0, 245, 255, 0.15);
}
h1, .panel h2.main-title {
  color: #fff;
  text-shadow: 0 0 25px rgba(0, 245, 255, 0.6), 0 0 10px rgba(255, 43, 214, 0.3);
  margin: 0 0 12px;
  font-size: clamp(28px, 8vw, 38px);
  letter-spacing: 2px;
  font-weight: 800;
  font-style: italic;
}
.menu-desc { color: #94a3b8; font-size: 13.5px; line-height: 1.5; margin-bottom: 15px; padding: 0 10px; }
.menu-hints {
  background: rgba(3, 4, 12, 0.5);
  border: 1px dashed rgba(255, 255, 255, 0.15);
  border-radius: 10px;
  padding: 10px 14px;
  font-size: 12px;
  color: #cbd5e1;
  margin-bottom: 18px;
}
.btn-play {
  background: linear-gradient(135deg, #00bcd4 0%, #008ba3 100%) !important;
  color: #03040c !important;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: 1px;
  border: 1px solid #b9faff !important;
  box-shadow: 0 0 25px rgba(0, 188, 212, 0.5) !important;
  transition: all 0.3s ease;
}
.btn-play:hover {
  background: linear-gradient(135deg, #33d4e8 0%, #00bcd4 100%) !important;
  box-shadow: 0 0 35px rgba(0, 188, 212, 0.8) !important;
  transform: translateY(-2px) scale(1.02);
}

#topbar {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
  font-size: 13px;
}
.stat {
  flex: 1;
  background: rgba(17, 21, 42, 0.8);
  padding: 8px 4px;
  border: 1px solid rgba(40, 46, 82, 0.8);
  border-radius: 12px;
  backdrop-filter: blur(5px);
}
.stat strong { font-size: 15px; color: var(--cyan); }

#game {
  width: 100%;
  height: min(64vh, 600px);
  min-height: 380px;
  position: relative;
  overflow: hidden;
  background: #060917;
  border: 2px solid var(--cyan);
  border-radius: 18px;
  box-shadow: 0 0 25px rgba(0, 245, 255, 0.25);
  touch-action: pan-y;
}
#road {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg, #0c1020 0%, #0c1020 3%, #151c33 3%, #151c33 97%, #0c1020 97%, #0c1020 100%);
}
.lane-line {
  position: absolute;
  top: -100%;
  height: 200%;
  width: 3px;
  background: repeating-linear-gradient(to bottom, #4f5d82 0 30px, transparent 30px 60px);
  opacity: .5;
}
#line1 { left: 33.333%; }
#line2 { left: 66.666%; }
.car {
  position: absolute;
  width: 48px;
  height: 76px;
  border-radius: 12px 12px 10px 10px;
  border: 2px solid rgba(255,255,255,0.9);
  box-shadow: 0 0 15px currentColor;
  transform: translateX(-50%);
  z-index: 4;
}
.car::before {
  content: "";
  position: absolute;
  width: 28px;
  height: 24px;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  background: #d4ffff;
  border-radius: 6px 6px 3px 3px;
  border: 1px solid #ffffff;
}
.car::after {
  content: "";
  position: absolute;
  bottom: 8px; left: 8px; right: 8px; height: 6px;
  border-radius: 3px;
  background: #ff365e;
  box-shadow: 0 0 10px #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: 26px; filter: drop-shadow(0 0 10px currentColor); }
#effects { position: absolute; z-index: 7; top: 10px; left: 10px; right: 10px; display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; pointer-events: none; }
.effect { padding: 4px 10px; background: rgba(17, 24, 45, 0.85); border: 1px solid var(--cyan); border-radius: 20px; font-size: 11px; font-weight: 600; }
#shieldRing { position: absolute; width: 62px; height: 90px; border: 2px dashed var(--cyan); box-shadow: 0 0 15px var(--cyan); border-radius: 16px; bottom: 23px; 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: 16px;
  background: rgba(3, 5, 15, 0.85);
  backdrop-filter: blur(8px);
}
.panel {
  width: 100%;
  max-height: 100%;
  overflow-y: auto;
  background: rgba(13, 18, 38, 0.95);
  border: 1px solid rgba(0, 245, 255, 0.3);
  border-radius: 20px;
  padding: 22px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
}
.panel h2 { color: var(--cyan); margin: 0 0 10px; font-size: 22px; }
.panel p { line-height: 1.5; color: #cbd5e1; font-size: 14px; }
.hidden { display: none !important; }
.menu-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 15px;
}
.menu-buttons button { width: 100%; }
.wide { grid-column: 1 / -1; }
#controls { display: flex; gap: 10px; margin-top: 12px; }
#controls button { flex: 1; font-size: 18px; padding: 14px; }
#message { min-height: 22px; margin: 10px 0 2px; font-size: 13px; color: #93c5fd; font-weight: 500; }
.upgrade-card { border: 1px solid rgba(52, 61, 98, 0.6); border-radius: 14px; padding: 12px; margin: 10px 0; text-align: left; background: rgba(10, 16, 34, 0.7); }
.upgrade-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.upgrade-card p { color: #94a3b8; font-size: 12px; margin: 6px 0 10px; }
.level-bar { display: flex; gap: 4px; margin: 8px 0; }
.level-bar span { height: 6px; flex: 1; background: #1e2642; border-radius: 4px; }
.level-bar span.on { background: var(--cyan); box-shadow: 0 0 8px var(--cyan); }
.small { font-size: 12px; color: #94a3b8; }
#footer { margin-top: 8px; color: #64748b; font-size: 11px; letter-spacing: 0.5px; }
</style>
</head>
<body>

<!-- FAKE LOADER OVERLAY -->
<div id="loaderOverlay">
  <div class="loader-content">
    <div class="loader-title">NEON RACER X</div>
    <div class="loader-subtitle">SYSTEM LÄDT...</div>
    <div class="loader-bar-bg">
      <div class="loader-bar-fill" id="loaderBar"></div>
    </div>
    <div class="loader-percentage" id="loaderText">0%</div>
  </div>
</div>

<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">
      <!-- HAUPTMENÜ -->
      <div class="panel" id="mainMenu">
        <div class="logo-container">
          <div class="logo-badge">Cyber Arcade Edition</div>
          <h2 class="main-title">NEON RACER X</h2>
        </div>
        <p class="menu-desc">Weiche Hindernissen aus, sammle Münzen und rüste deine Power-ups auf, um neue Rekorde aufzustellen!</p>
        
        <div class="menu-hints">
          ⌨️ <strong>Steuerung:</strong> ← → / A & D · Touch / Wischen auf Mobile
        </div>

        <div class="menu-buttons">
          <button class="wide btn-play" id="startBtn">▶ SPIEL STARTEN</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 permanent.</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>
        <div class="menu-buttons" style="margin-top: 15px;">
          <button class="wide" id="resumeBtn">Weiter spielen</button>
          <button class="wide" id="pauseHomeBtn">Hauptmenü</button>
        </div>
      </div>

      <div class="panel hidden" id="overMenu">
        <h2>💥 Game Over</h2>
        <p>Dein Score: <strong id="finalScore" style="color:var(--cyan)">0</strong></p>
        <p>Gesammelte Münzen: <strong id="finalCoins" style="color:var(--cyan)">0</strong></p>
        <div class="menu-buttons" style="margin-top: 15px;">
          <button class="wide" id="againBtn">🔄 Nochmal spielen</button>
          <button class="wide" id="overHomeBtn">Hauptmenü</button>
        </div>
      </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");

/* --- FAKE LOADER LOGIK --- */
function initLoader() {
  const bar = $("loaderBar");
  const text = $("loaderText");
  const overlayEl = $("loaderOverlay");

  let progress = 0;
  const startTime = performance.now();
  const duration = 2000; // 2 Sekunden bis 99%

  function updateLoader(now) {
    const elapsed = now - startTime;
    
    if (progress < 99) {
      progress = Math.min(99, Math.floor((elapsed / duration) * 99));
      bar.style.width = progress + "%";
      text.textContent = progress + "%";
      requestAnimationFrame(updateLoader);
    } else {
      // Bei 99% angekommen: 1 Sekunde warten, dann auf 100% setzen
      bar.style.width = "99%";
      text.textContent = "99%";
      
      setTimeout(() => {
        progress = 100;
        bar.style.width = "100%";
        text.textContent = "100%";

        // Bei 100% angekommen: Noch 1 Sekunde stehen bleiben, dann ausblenden
        setTimeout(() => {
          overlayEl.classList.add("fade-out");
        }, 1000);

      }, 1000);
    }
  }

  requestAnimationFrame(updateLoader);
}

// Loader direkt beim Start ausführen
window.addEventListener("DOMContentLoaded", initLoader);

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 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;
}

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) + "s · Magnet: " + (8 + level * 3) + "s";
    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);
  }
}

function buyUpgrade(level) {
  if (level !== upgradeLevel + 1 || level < 1 || level > 5) return;
  const price = UPGRADE_PRICES[level - 1];
  if (coins < price) return;
  coins -= price;
  upgradeLevel = level;
  saveAll();
  updateHUD();
  renderUpgrades();
  setMessage("Upgrade " + level + " gekauft!");
}

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);
  });
}

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();
}

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!");
  } else if (type === "magnet") {
    magnetTime = currentMagnetDuration();
    setMessage("🧲 Magnet aktiviert!");
  }
}
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;

  const speed = Math.min(520, 230 + score * .018);
  const spawnInterval = Math.max(.62, 1.05 - score * .000025);

  roadOffset = (roadOffset + speed * dt) % 60;
  $("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(); }

  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); }
  }

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

$("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));

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

Game Source: Neon Racer X

Creator: BlazeKoala99

Libraries: none

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