const isTouchDevice = ("ontouchstart" in window) || navigator.maxTouchPoints > 0;

const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;

// -------------------------------------------------------------
// LADEBILDSCHIRM: bleibt sichtbar, bis Verbindung + Login + alle
// Grunddaten (Autos, Objekte, Items, Kleidung, Radio, Tiles) da sind
// -------------------------------------------------------------
const LOADING_STEPS = ["auth", "playerState", "carConfigs", "objectConfig", "items", "clothingCatalog", "radioStations", "tileConfig"];
const loadingStepsDone = new Set();
let playerStateLoaded = false; // wird nur beim allerersten vollständigen state-Update gesetzt

function markLoadingStepDone(step, statusText) {
    loadingStepsDone.add(step);
    updateLoadingProgress(statusText);
    if (loadingStepsDone.size >= LOADING_STEPS.length) {
        hideLoadingScreen();
    }
}

function updateLoadingProgress(statusText) {
    const bar = document.getElementById("loadingBarInner");
    const text = document.getElementById("loadingStatusText");
    if (bar) bar.style.width = Math.round((loadingStepsDone.size / LOADING_STEPS.length) * 100) + "%";
    if (text && statusText) text.textContent = statusText;
}

function showLoadingScreen() {
    const el = document.getElementById("loadingScreen");
    if (!el) return;
    el.classList.remove("fading");
    el.style.display = "flex";
    updateLoadingProgress("Verbindung wird hergestellt...");
}

function hideLoadingScreen() {
    const el = document.getElementById("loadingScreen");
    if (!el) return;
    el.classList.add("fading");
    setTimeout(() => { el.style.display = "none"; }, 400);
}

// -------------------------------------------------------------
// CANVAS-GRÖSSE: dynamisch, kein linker Rand, rechts Platz für die
// UI-Fenster (Status/Inventar/Shop/ATM/Garage) lassen
// -------------------------------------------------------------
const TOPMENU_HEIGHT = 50; // muss zu #topMenu in game.html passen
const SIDEBAR_WIDTH = 260; // Platz rechts für die 220px breiten Fenster + Puffer
const CHAT_HEIGHT = 180;   // Platz unten für den Chat

function resizeCanvas() {
    // Auf Touch-Geräten liegt der Chat unter/über der Steuerung und die
    // Seitenfenster werden nacheinander statt permanent nebeneinander genutzt -
    // deswegen wird dort kein fester Platz mehr reserviert, die Karte nutzt den vollen Bildschirm
    const sidebarReserve = isTouchDevice ? 0 : SIDEBAR_WIDTH;
    const chatReserve = isTouchDevice ? 0 : CHAT_HEIGHT;
    canvas.width = Math.max(320, window.innerWidth - sidebarReserve);
    canvas.height = Math.max(240, window.innerHeight - TOPMENU_HEIGHT - chatReserve);
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);

// -------------------------------------------------------------
// CHAT
// -------------------------------------------------------------
const chatMessagesEl = document.getElementById("chatMessages");
const chatInput = document.getElementById("chatInput");
const chatSendBtn = document.getElementById("chatSendBtn");

function escapeHtml(str) {
    const div = document.createElement("div");
    div.textContent = str;
    return div.innerHTML;
}

function addChatMessage(text, kind, from) {
    const line = document.createElement("div");
    line.className = "chat-line " + (kind === "system" ? "system" : kind === "radio" ? "radio" : "player");

    if (kind === "system" || kind === "radio") {
        line.innerHTML = escapeHtml(text);
    } else {
        line.innerHTML = `<span class="name">${escapeHtml(from || "???")}:</span> ${escapeHtml(text)}`;
    }

    chatMessagesEl.appendChild(line);
    chatMessagesEl.scrollTop = chatMessagesEl.scrollHeight;

    // Chat-Verlauf begrenzen, damit das DOM nicht unbegrenzt wächst
    while (chatMessagesEl.children.length > 200) {
        chatMessagesEl.removeChild(chatMessagesEl.firstChild);
    }
}

let debugMode = false;

function sendChatMessage() {
    const text = chatInput.value.trim();
    if (!text) return;

    chatInput.value = "";

    if (text.startsWith("/")) {
        handleChatCommand(text);
        return;
    }

    if (!ws || ws.readyState !== WebSocket.OPEN) return;
    ws.send(JSON.stringify({ type: "chat_send", text }));
}

function handleChatCommand(text) {
    const raw = text.slice(1).trim();
    const cmd = raw.toLowerCase();

    if (cmd === "map") {
        openWorldMap();
        return;
    }

    if (cmd === "navi cancel") {
        naviTarget = null;
        updateNaviDisplay();
        addChatMessage("Navigation abgebrochen.", "system");
        return;
    }

    if (cmd === "help") {
        const lines = [
            "📖 Verfügbare Befehle:",
            "/commands - alle Befehle als klickbares Fenster mit Eingabefeldern öffnen",
            "/help - diese Liste anzeigen",
            "ℹ️ Bei allen Befehlen, die einen Spieler-Namen erwarten, kannst du stattdessen auch die Spieler-ID eingeben (z.B. /cuff 42 statt /cuff Name).",
            "/map - Weltkarte mit allen Orten öffnen",
            "J (Taste) - Job-Menü öffnen (Taxi/Polizei/Sanitäter/Abschlepper)",
            "V (Taste) - Mikrofon stummschalten/aktivieren (Sprachchat)",
            "🎮 Controller wird automatisch erkannt - linker Stick/D-Pad = Bewegen, A/Kreuz = Interagieren, X/Viereck = Job-Menü, Y/Dreieck = Handy",
            "/navi cancel - aktive Navigation abbrechen",
            "/debug - Debug-Overlay (Koordinaten) ein/aus",
            "/key Name - Autoschlüssel geben (im eigenen Auto)",
            "/key remove Name - Autoschlüssel entziehen",
            "/housekey Name - Hausschlüssel geben (im/am eigenen Haus)",
            "/housekey remove Name - Hausschlüssel entziehen",
            "/rob - Raubüberfall (in Shop-/Bank-Nähe)",
            "/hotwire - Auto kurzschließen (neben fremdem Auto)",
            "/arrest Name - Spieler verhaften (nur Polizei-Job)",
            "/pvp - zeigt an, ob du gerade in einer Sicherheitszone bist (PvP ist überall sonst automatisch aktiv)",
            "/attack - mit erster Waffe im Inventar angreifen (nur bei PvP an, Ziel in Reichweite)",
            "/gang create Name Tag - Bande gründen (5000$)",
            "/gang invite Name / accept Bandenname / kick Name / leave",
            "/gang deposit/withdraw Betrag - Bandenkasse verwalten",
            "/gang info - Bandeninfo anzeigen",
            "/job invite Name - in geschützten Job aufnehmen (nur wenn du selbst diesen Job hast)",
            "/tow hook - freistehendes Fahrzeug anhängen (nur Abschlepper-Job, im Abschleppwagen)",
            "/tow dropoff - angehängtes Fahrzeug am Abschlepphof abliefern (+50$)",
            "/tow release - Fahrzeug loslassen ohne abzuliefern",
            "/shop buy / unclaim / info - Shop kaufen/freigeben/Besitzer anzeigen (am Shop stehend)",
            "/station buy / unclaim / info - Tankstelle kaufen/freigeben/Besitzer anzeigen (an der Tankstelle stehend)",
            "/car insure / uninsure - eigenes Auto (an)versichern (kleine periodische Gebühr, Rabatt bei Totalschaden-Reparatur)",
            "/tune - Tuning-Menü öffnen (an der Werkstatt, im eigenen Auto): Motor/Beschleunigung/Bremsen aufwerten, Lackierung",
            "/trailer buy - Anhänger kaufen (steht neben dir)",
            "/trailer hitch - eigenen Anhänger in der Nähe ankuppeln (mit jedem Fahrzeug)",
            "K (Taste) - Anhänger an-/abkuppeln (automatisch je nach Zustand)",
            "/trailer unhitch - angekuppelten Anhänger lösen",
            "/trailer load - dein Auto (in dem du sitzt) auf einen nahen Anhänger laden",
            "/trailer unload - aufgeladenes Auto wieder vom Anhänger holen",
            "/plate TEXT - Nummernschild für dein Auto setzen (kostet etwas, 2-10 Zeichen)",
            "/house setrent Betrag - dein eigenes Haus zur Miete anbieten (0 zum Zurückziehen)",
            "/house rent - ein angebotenes Haus mieten (musst davor stehen)",
            "/house evict - Mieter deines Hauses kündigen (als Besitzer)",
            "/house moveout - selbst aus einem gemieteten Haus ausziehen",
            "/bounty Name Betrag - Kopfgeld auf einen Spieler aussetzen (nur bei echtem PvP-Kill auszahlbar)",
            "/bounty info Name - aktuelles Kopfgeld auf jemanden anzeigen",
            "/cuff Name - Handschellen anlegen (nur Polizei, Person muss gesucht sein)",
            "/uncuff Name - Handschellen abnehmen",
            "/putin Name - gefesselte Person in dein Fahrzeug setzen",
            "/takeout Name - gefesselte Person aus dem Fahrzeug holen",
            "/jaildropoff Name - gefesselte Person am Gefängnis abliefern (muss bei dir im Auto sitzen)",
            "/funk Nachricht - Funkspruch an alle mit demselben Job (nur Polizei/Sanitäter/Feuerwehr)",
            "P (Taste) / /phone - Handy öffnen (Kontakte, SMS)",
            "O (Taste) / /tablet - Einsatz-Tablet öffnen (nur Polizei/Sanitäter/Feuerwehr): Personensuche, Fahndungsliste, Kennzeichen-Abfrage",
            "/ticket - Support-Tickets öffnen (eigene Anliegen ans Team melden)",
            "/controller - Controller-Einstellungen öffnen (Ein/Aus, Tasten neu zuordnen)",
            "/börse - Börse öffnen (Aktien kaufen/verkaufen, Portfolio ansehen)",
            "/verkaufen - gestohlenes Auto beim Hehler verkaufen (musst dort stehen + im Auto sitzen) - Aufpreis, aber Entdeckungsrisiko!",
            "/leitstelle - Leitstellen-Übersicht öffnen (nur Polizei/Rettungsdienst/Feuerwehr): alle Einheiten im Dienst + FMS-Status, Einsätze verteilen",
            "/phone add Name - Kontaktanfrage senden",
            "/phone accept Name - Kontaktanfrage annehmen",
            "/phone remove Name - Kontakt entfernen",
            "/sms Name Nachricht - SMS senden",
            "/call Name - anrufen (nur wenn online, funktioniert über jede Entfernung)",
            "/fire extinguish - nächstes Feuer löschen (nur Feuerwehr-Job)",
            "Drogen: E an 🌿 Anbaustelle (ernten), ⚗️ Labor (verarbeiten), 💰 Verkaufsort (verkaufen, wechselt danach Ort+Preis)",
            "/gate claim - Tor beanspruchen (wird Besitzer)",
            "/gate unclaim - Tor wieder freigeben",
            "/gate info - Besitzer/Schlüssel-Inhaber anzeigen",
            "/gatekey Name - Schlüssel vergeben (nur Besitzer, am Tor stehen)",
            "/gatekey remove Name - Schlüssel entziehen"
        ];
        if (player.isAdmin) {
            lines.push("/admin - Admin-Menü öffnen");
            lines.push("/restart [Sekunden] - Server-Neustart ankündigen (Standard 60s)");
            lines.push("/restart cancel - laufenden Neustart abbrechen");
            lines.push("/admin duty - Dienstmodus (jedes Auto fahren) an/aus");
            lines.push("/gate setjob Jobname - Tor auf einen Job beschränken (am Tor stehen, 'none' zum Entfernen)");
        }
        lines.forEach(line => addChatMessage(line, "system"));
        return;
    }

    if (cmd === "admin") {
        if (!player.isAdmin) {
            addChatMessage("Kein Admin-Zugriff.", "system");
            return;
        }
        openAdminMenu();
        return;
    }

    if (cmd === "debug") {
        debugMode = !debugMode;
        document.getElementById("debugBox").style.display = debugMode ? "block" : "none";
        addChatMessage(`Debug-Fenster ${debugMode ? "eingeschaltet" : "ausgeschaltet"}.`, "system");
        return;
    }

    if (cmd === "commands") {
        openCommandsWindow();
        return;
    }

    if (cmd.startsWith("key remove ")) {
        const username = raw.slice(11).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "car_revoke_key", username }));
        return;
    }
    if (cmd.startsWith("key ")) {
        const username = raw.slice(4).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "car_give_key", username }));
        return;
    }

    if (cmd.startsWith("housekey remove ")) {
        const username = raw.slice(16).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "house_revoke_key", username }));
        return;
    }
    if (cmd.startsWith("housekey ")) {
        const username = raw.slice(9).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "house_give_key", username }));
        return;
    }

    if (cmd === "rob") {
        ws.send(JSON.stringify({ type: "commit_crime", crimeType: "robbery" }));
        return;
    }
    if (cmd === "hotwire") {
        ws.send(JSON.stringify({ type: "commit_crime", crimeType: "car_theft" }));
        return;
    }

    if (cmd.startsWith("gang ") || cmd === "gang") {
        const parts = raw.split(" ");
        const sub = parts[1] ? parts[1].toLowerCase() : "";
        const rest = parts.slice(2);

        if (sub === "create") {
            if (rest.length < 2) {
                addChatMessage("Nutzung: /gang create Name Tag (z.B. /gang create Street Kings SK)", "system");
                return;
            }
            const tag = rest[rest.length - 1];
            const name = rest.slice(0, -1).join(" ");
            ws.send(JSON.stringify({ type: "gang_create", name, tag }));
            return;
        }
        if (sub === "invite") {
            const username = rest.join(" ");
            if (!username) return;
            ws.send(JSON.stringify({ type: "gang_invite", username }));
            return;
        }
        if (sub === "accept") {
            const gangName = rest.join(" ");
            if (!gangName) return;
            ws.send(JSON.stringify({ type: "gang_accept", gangName }));
            return;
        }
        if (sub === "kick") {
            const username = rest.join(" ");
            if (!username) return;
            ws.send(JSON.stringify({ type: "gang_kick", username }));
            return;
        }
        if (sub === "leave") {
            ws.send(JSON.stringify({ type: "gang_leave" }));
            return;
        }
        if (sub === "deposit") {
            const amount = Number(rest[0]);
            if (!amount || amount <= 0) return;
            ws.send(JSON.stringify({ type: "gang_deposit", amount }));
            return;
        }
        if (sub === "withdraw") {
            const amount = Number(rest[0]);
            if (!amount || amount <= 0) return;
            ws.send(JSON.stringify({ type: "gang_withdraw", amount }));
            return;
        }
        if (sub === "info") {
            ws.send(JSON.stringify({ type: "gang_info" }));
            return;
        }

        addChatMessage("Unbekannter /gang Befehl. Siehe /help", "system");
        return;
    }

    if (cmd === "gate claim") {
        ws.send(JSON.stringify({ type: "gate_claim" }));
        return;
    }
    if (cmd === "gate unclaim") {
        ws.send(JSON.stringify({ type: "gate_unclaim" }));
        return;
    }
    if (cmd === "gate info") {
        ws.send(JSON.stringify({ type: "gate_info" }));
        return;
    }
    if (cmd.startsWith("gate setjob ")) {
        const jobName = raw.slice(12).trim();
        if (!jobName) return;
        ws.send(JSON.stringify({ type: "gate_set_job", jobName }));
        return;
    }

    if (cmd === "tow hook") {
        ws.send(JSON.stringify({ type: "tow_hook" }));
        return;
    }
    if (cmd === "tow dropoff") {
        ws.send(JSON.stringify({ type: "tow_dropoff" }));
        return;
    }
    if (cmd === "tow release") {
        ws.send(JSON.stringify({ type: "tow_release" }));
        return;
    }

    if (cmd === "shop buy") {
        ws.send(JSON.stringify({ type: "shop_claim" }));
        return;
    }
    if (cmd === "shop unclaim") {
        ws.send(JSON.stringify({ type: "shop_unclaim" }));
        return;
    }
    if (cmd === "shop info") {
        ws.send(JSON.stringify({ type: "shop_owner_info" }));
        return;
    }
    if (cmd === "station buy") {
        ws.send(JSON.stringify({ type: "station_claim" }));
        return;
    }
    if (cmd === "station unclaim") {
        ws.send(JSON.stringify({ type: "station_unclaim" }));
        return;
    }
    if (cmd === "station info") {
        ws.send(JSON.stringify({ type: "station_owner_info" }));
        return;
    }

    if (cmd === "car insure") {
        ws.send(JSON.stringify({ type: "car_insure" }));
        return;
    }
    if (cmd === "car uninsure") {
        ws.send(JSON.stringify({ type: "car_uninsure" }));
        return;
    }

    if (cmd === "tune") {
        ws.send(JSON.stringify({ type: "tune_menu_open" }));
        return;
    }

    if (cmd.startsWith("bounty info ")) {
        const username = raw.slice(12).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "bounty_info", username }));
        return;
    }

    if (cmd.startsWith("funk ")) {
        const text = raw.slice(5).trim();
        if (!text) return;
        ws.send(JSON.stringify({ type: "radio_message", text }));
        return;
    }

    if (cmd === "phone") {
        openPhone();
        return;
    }
    if (cmd === "tablet") {
        openTablet();
        return;
    }
    if (cmd === "ticket" || cmd === "tickets") {
        openTicketBox();
        return;
    }
    if (cmd === "controller" || cmd === "gamepad") {
        openGamepadSettings();
        return;
    }
    if (cmd === "börse" || cmd === "boerse" || cmd === "stocks") {
        openStockBox();
        return;
    }
    if (cmd === "verkaufen" || cmd === "hehler") {
        ws.send(JSON.stringify({ type: "sell_stolen_car" }));
        return;
    }
    if (cmd === "leitstelle") {
        openLeitstelle();
        return;
    }
    if (cmd.startsWith("phone add ")) {
        const username = raw.slice(10).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "phone_add_contact", username }));
        return;
    }
    if (cmd.startsWith("phone accept ")) {
        const username = raw.slice(13).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "phone_accept_contact", username }));
        return;
    }
    if (cmd.startsWith("phone remove ")) {
        const username = raw.slice(13).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "phone_remove_contact", username }));
        return;
    }
    if (cmd.startsWith("sms ")) {
        const parts = raw.slice(4).trim().split(" ");
        const username = parts.shift();
        const text = parts.join(" ").trim();
        if (!username || !text) {
            addChatMessage("Nutzung: /sms Name Nachricht", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "phone_send_sms", username, text }));
        return;
    }
    if (cmd.startsWith("call ")) {
        const username = raw.slice(5).trim();
        if (!username) return;
        initiateCall(username);
        return;
    }

    if (cmd.startsWith("cuff ")) {
        const username = raw.slice(5).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "police_cuff", username }));
        return;
    }
    if (cmd.startsWith("uncuff ")) {
        const username = raw.slice(7).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "police_uncuff", username }));
        return;
    }
    if (cmd.startsWith("putin ")) {
        const username = raw.slice(6).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "police_put_in_car", username }));
        return;
    }
    if (cmd.startsWith("takeout ")) {
        const username = raw.slice(8).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "police_take_out_of_car", username }));
        return;
    }
    if (cmd.startsWith("jaildropoff ")) {
        const username = raw.slice(12).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "police_jail_dropoff", username }));
        return;
    }

    if (cmd.startsWith("bounty ")) {
        const parts = raw.slice(7).trim().split(" ");
        const amount = Number(parts.pop());
        const username = parts.join(" ").trim();
        if (!username || !amount) {
            addChatMessage("Nutzung: /bounty Name Betrag", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "bounty_place", username, amount }));
        return;
    }

    if (cmd === "trailer buy") {
        ws.send(JSON.stringify({ type: "trailer_buy" }));
        return;
    }
    if (cmd === "trailer hitch") {
        ws.send(JSON.stringify({ type: "trailer_hitch" }));
        return;
    }
    if (cmd === "trailer unhitch") {
        ws.send(JSON.stringify({ type: "trailer_unhitch" }));
        return;
    }
    if (cmd === "trailer load") {
        ws.send(JSON.stringify({ type: "trailer_load_car" }));
        return;
    }
    if (cmd === "trailer unload") {
        ws.send(JSON.stringify({ type: "trailer_unload_car" }));
        return;
    }

    if (cmd.startsWith("plate ")) {
        const plate = raw.slice(6).trim();
        if (!plate) return;
        ws.send(JSON.stringify({ type: "car_set_plate", plate }));
        return;
    }

    if (cmd.startsWith("house setrent ")) {
        const price = Number(raw.slice(14).trim());
        if (isNaN(price) || price < 0) {
            addChatMessage("Nutzung: /house setrent Betrag (0 zum Zurückziehen)", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "house_set_rent", rentPrice: price }));
        return;
    }
    if (cmd === "house unrent") {
        ws.send(JSON.stringify({ type: "house_set_rent", rentPrice: 0 }));
        return;
    }
    if (cmd === "house rent") {
        ws.send(JSON.stringify({ type: "house_rent" }));
        return;
    }
    if (cmd === "house evict" || cmd === "house moveout") {
        ws.send(JSON.stringify({ type: "house_end_rental" }));
        return;
    }

    if (cmd === "fire extinguish") {
        if (fires.length === 0) {
            addChatMessage("Kein Feuer in der Nähe.", "system");
            return;
        }
        // nächstes Feuer zur eigenen Position finden
        let closest = null;
        let closestDist = Infinity;
        fires.forEach(f => {
            const d = Math.hypot(f.x - player.x, f.y - player.y);
            if (d < closestDist) { closestDist = d; closest = f; }
        });
        if (closest) ws.send(JSON.stringify({ type: "fire_extinguish", fireId: closest.id }));
        return;
    }

    if (cmd.startsWith("gatekey remove ")) {
        const username = raw.slice(15).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "gate_revoke_key", username }));
        return;
    }
    if (cmd.startsWith("gatekey ")) {
        const username = raw.slice(8).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "gate_give_key", username }));
        return;
    }

    if (cmd.startsWith("job invite ")) {
        const username = raw.slice(11).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "job_invite", username }));
        return;
    }

    if (cmd === "admin duty") {
        if (!player.isAdmin) {
            addChatMessage("Kein Admin-Zugriff.", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "admin_toggle_duty" }));
        return;
    }

    if (cmd.startsWith("arrest ")) {
        const username = raw.slice(7).trim();
        if (!username) return;
        ws.send(JSON.stringify({ type: "arrest_player", username }));
        return;
    }

    if (cmd === "restart cancel") {
        if (!player.isAdmin) {
            addChatMessage("Kein Admin-Zugriff.", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "admin_restart_server", cancel: true }));
        return;
    }
    if (cmd.startsWith("restart")) {
        if (!player.isAdmin) {
            addChatMessage("Kein Admin-Zugriff.", "system");
            return;
        }
        const parts = raw.split(" ");
        const seconds = parts[1] ? Number(parts[1]) : 60;
        ws.send(JSON.stringify({ type: "admin_restart_server", seconds }));
        return;
    }

    if (cmd === "pvp") {
        ws.send(JSON.stringify({ type: "toggle_pvp" }));
        return;
    }

    if (cmd === "attack") {
        const weapon = (player.inventory || []).find(i => {
            const info = itemInfoCache[i.id];
            return info && (info.type === "weapon_melee" || info.type === "weapon_ranged");
        });
        if (!weapon) {
            addChatMessage("Du hast keine Waffe im Inventar.", "system");
            return;
        }
        ws.send(JSON.stringify({ type: "attack", weaponId: weapon.id }));
        return;
    }

    addChatMessage(`Unbekannter Befehl: ${text} (probier /help)`, "system");
}

function updateDebugBox() {
    if (!debugMode) return;

    const tileX = Math.floor(player.x / 32);
    const tileY = Math.floor(player.y / 32);
    const cam = getCamera();

    document.getElementById("dbgWorld").textContent = player.world;
    document.getElementById("dbgPos").textContent = `${Math.round(player.x)}, ${Math.round(player.y)}`;
    document.getElementById("dbgTile").textContent = `${tileX}, ${tileY}`;
    document.getElementById("dbgCam").textContent = `${cam.x}, ${cam.y}`;
    document.getElementById("dbgFacing").textContent = drivingCarId ? "im Auto" : "zu Fuß";
}

chatSendBtn.addEventListener("click", sendChatMessage);
chatInput.addEventListener("keydown", e => {
    if (e.key === "Enter") {
        e.preventDefault();
        sendChatMessage();
    }
    e.stopPropagation(); // verhindert, dass Tippen im Chat die Spielsteuerung (WASD) auslöst
});

const loginBox = document.getElementById("loginBox");
const registerBox = document.getElementById("registerBox");

const loginUser = document.getElementById("loginUser");
const loginPass = document.getElementById("loginPass");
const loginBtn = document.getElementById("loginBtn");

const regUser = document.getElementById("regUser");
const regPass = document.getElementById("regPass");
const regBtn = document.getElementById("regBtn");

const switchToRegister = document.getElementById("switchToRegister");
const switchToLogin = document.getElementById("switchToLogin");

// Falls bereits ein Token aus der Startseite (index.html) im Browser liegt,
// Login-Formular gar nicht erst anzeigen (Aufblitzen vermeiden)
if (localStorage.getItem("token")) {
    loginBox.style.display = "none";
}

const shopWindow = document.getElementById("shopWindow");
const shopList = document.getElementById("shopList");

let ws = null;
let maps = {};
let tileConfig = {};
let lastServerUpdate = 0;

let player = {
    id: null,
    x: 100,
    y: 100,
    world: "stadt"
};
player.health = 100;
player.hunger = 100;
player.thirst = 100;

player.inventory = [];
player.money = 1000;

let loggedIn = false;
let otherPlayers = [];
let keys = {};

// -------------------------------------------------------------
// GAMEPAD-UNTERSTÜTZUNG
// -------------------------------------------------------------
// Bewegung/Fahren: linker Stick + D-Pad speisen direkt keys["w"/"a"/"s"/"d"],
// dieselben Variablen wie die Tastatur - die bestehende Bewegungs-/Fahrlogik
// merkt also gar nicht, ob Tastatur oder Controller benutzt wird.
//
// Aktions-Tasten (E, J, P, V, usw.): simulieren echte Tastatur-Events, damit
// alle bereits existierenden keydown-Handler automatisch mitfunktionieren,
// ohne die Logik doppelt pflegen zu müssen.
const GAMEPAD_STICK_DEADZONE = 0.3;
const GAMEPAD_DEFAULT_BUTTON_KEY_MAP = {
    0: "e",        // A / Kreuz - interagieren, einsteigen
    1: "Escape",   // B / Kreis - Fenster schließen
    2: "j",        // X / Viereck - Job-Menü
    3: "p",        // Y / Dreieck - Handy
    4: "1",        // linke Schulter - Blinker links
    5: "2",        // rechte Schulter - Blinker rechts
    6: "q",        // linker Trigger - Tanken/Reparieren/Tor
    7: "5",        // rechter Trigger - Blaulicht
    8: "v",        // Select/Back - Mikro stumm
    9: "m",        // Start - (falls belegt, sonst ignoriert)
    12: "ArrowUp",    // D-Pad hoch (zusätzlich zum Stick, z.B. für Kamera/Scrollen im Editor)
    13: "ArrowDown",
    14: "ArrowLeft",
    15: "ArrowRight"
};

// Beschriftungen für die Einstellungs-Oberfläche: welche Aktion liegt hinter welcher Taste
const GAMEPAD_ACTION_LABELS = {
    "e": "Interagieren / Einsteigen",
    "Escape": "Fenster schließen",
    "j": "Job-Menü",
    "p": "Handy",
    "1": "Blinker links",
    "2": "Blinker rechts",
    "q": "Tanken / Reparieren / Tor",
    "5": "Blaulicht",
    "v": "Mikro stumm/an"
};

// Eigene Zuordnung aus dem Browser laden, sonst Standard nehmen
let GAMEPAD_BUTTON_KEY_MAP = { ...GAMEPAD_DEFAULT_BUTTON_KEY_MAP };
try {
    const savedMap = localStorage.getItem("gamepadButtonMap");
    if (savedMap) GAMEPAD_BUTTON_KEY_MAP = { ...GAMEPAD_DEFAULT_BUTTON_KEY_MAP, ...JSON.parse(savedMap) };
} catch {}

let gamepadEnabled = localStorage.getItem("gamepadEnabled") !== "false"; // standardmäßig an

let gamepadButtonState = {}; // Index -> war im letzten Frame gedrückt?
let gamepadConnected = false;
let gamepadMove = { w: false, a: false, s: false, d: false }; // eigener Status, wird jeden Poll frisch gesetzt

// -------------------------------------------------------------
// MOBILE TOUCH-STEUERUNG (virtueller Joystick + Action-Buttons)
// -------------------------------------------------------------
let touchMove = { w: false, a: false, s: false, d: false };

if (isTouchDevice) {
    document.getElementById("touchControls").style.display = "block";
}

window.addEventListener("gamepadconnected", e => {
    gamepadConnected = true;
    addChatMessage(`🎮 Controller verbunden: ${e.gamepad.id}`, "system");
});
window.addEventListener("gamepaddisconnected", () => {
    gamepadConnected = false;
    gamepadButtonState = {};
    gamepadMove = { w: false, a: false, s: false, d: false };
    addChatMessage("🎮 Controller getrennt.", "system");
});

let gamepadRemapListenAction = null; // gesetzt, während auf einen Knopfdruck zum Neu-Zuordnen gewartet wird

function pollGamepad() {
    if (!gamepadEnabled) return;

    // Fallback: manche Browser feuern "gamepadconnected" nicht zuverlässig,
    // wenn der Controller schon vor dem Laden der Seite verbunden war
    if (!gamepadConnected) {
        const pads = navigator.getGamepads ? navigator.getGamepads() : [];
        if (pads[0]) {
            gamepadConnected = true;
            addChatMessage(`🎮 Controller erkannt: ${pads[0].id}`, "system");
        }
    }
    if (!gamepadConnected) return;

    const pads = navigator.getGamepads ? navigator.getGamepads() : [];
    const gp = pads[0];
    if (!gp) return;

    // Neu-Zuordnen-Modus: auf den nächsten gedrückten Knopf warten und zuordnen,
    // statt normal weiterzuspielen
    if (gamepadRemapListenAction) {
        for (let i = 0; i < gp.buttons.length; i++) {
            if (gp.buttons[i].pressed || gp.buttons[i].value > 0.5) {
                GAMEPAD_BUTTON_KEY_MAP[i] = gamepadRemapListenAction;
                localStorage.setItem("gamepadButtonMap", JSON.stringify(GAMEPAD_BUTTON_KEY_MAP));
                gamepadRemapListenAction = null;
                renderGamepadSettings();
                return;
            }
        }
        return;
    }

    // Linker Stick + D-Pad -> eigenes Bewegungs-Status-Objekt, jeden Poll frisch berechnet
    // (nicht direkt in keys[] schreiben, sonst kann der Zustand bei Loslassen "hängen bleiben")
    const stickX = gp.axes[0] || 0;
    const stickY = gp.axes[1] || 0;
    const dpadUp = !!(gp.buttons[12] && gp.buttons[12].pressed);
    const dpadDown = !!(gp.buttons[13] && gp.buttons[13].pressed);
    const dpadLeft = !!(gp.buttons[14] && gp.buttons[14].pressed);
    const dpadRight = !!(gp.buttons[15] && gp.buttons[15].pressed);

    gamepadMove.w = stickY < -GAMEPAD_STICK_DEADZONE || dpadUp;
    gamepadMove.s = stickY > GAMEPAD_STICK_DEADZONE || dpadDown;
    gamepadMove.a = stickX < -GAMEPAD_STICK_DEADZONE || dpadLeft;
    gamepadMove.d = stickX > GAMEPAD_STICK_DEADZONE || dpadRight;

    // Buttons: einmaliges Tastatur-Event simulieren beim Drücken, keys[] beim Loslassen zurücksetzen
    gp.buttons.forEach((btn, i) => {
        const mappedKey = GAMEPAD_BUTTON_KEY_MAP[i];
        if (!mappedKey) return;

        const isPressed = btn.pressed || btn.value > 0.5;
        const wasPressed = !!gamepadButtonState[i];

        if (isPressed && !wasPressed) {
            document.dispatchEvent(new KeyboardEvent("keydown", { key: mappedKey }));
        }
        if (!isPressed && wasPressed) {
            document.dispatchEvent(new KeyboardEvent("keyup", { key: mappedKey }));
        }
        gamepadButtonState[i] = isPressed;
    });
}

// -------------------------------------------------------------
// CONTROLLER-EINSTELLUNGEN: Ein/Aus + eigene Tastenzuordnung
// -------------------------------------------------------------
const GAMEPAD_BUTTON_NAMES = {
    0: "A / Kreuz", 1: "B / Kreis", 2: "X / Viereck", 3: "Y / Dreieck",
    4: "Linke Schulter (LB)", 5: "Rechte Schulter (RB)",
    6: "Linker Trigger (LT)", 7: "Rechter Trigger (RT)",
    8: "Select / Back", 9: "Start",
    12: "D-Pad hoch", 13: "D-Pad runter", 14: "D-Pad links", 15: "D-Pad rechts"
};

function openGamepadSettings() {
    document.getElementById("gamepadEnabledCheckbox").checked = gamepadEnabled;
    renderGamepadSettings();
    document.getElementById("gamepadSettingsBox").style.display = "flex";
}
function closeGamepadSettings() {
    gamepadRemapListenAction = null;
    document.getElementById("gamepadSettingsBox").style.display = "none";
}

function toggleGamepadEnabled() {
    gamepadEnabled = document.getElementById("gamepadEnabledCheckbox").checked;
    localStorage.setItem("gamepadEnabled", gamepadEnabled ? "true" : "false");
    if (!gamepadEnabled) {
        gamepadMove.w = gamepadMove.a = gamepadMove.s = gamepadMove.d = false;
    }
    addChatMessage(gamepadEnabled ? "🎮 Controller aktiviert." : "🎮 Controller deaktiviert.", "system");
}

function renderGamepadSettings() {
    const list = document.getElementById("gamepadActionList");
    list.innerHTML = "";

    for (const [action, label] of Object.entries(GAMEPAD_ACTION_LABELS)) {
        const currentIndex = Object.entries(GAMEPAD_BUTTON_KEY_MAP).find(([, key]) => key === action)?.[0];
        const currentName = currentIndex !== undefined ? (GAMEPAD_BUTTON_NAMES[currentIndex] || `Taste ${currentIndex}`) : "- keine -";
        const isListening = gamepadRemapListenAction === action;

        const row = document.createElement("div");
        row.className = "gamepad-action-row";
        row.innerHTML = `
            <span>${escapeHtmlAdmin(label)}</span>
            <span class="gamepad-current-btn">${escapeHtmlAdmin(currentName)}</span>
            <button class="${isListening ? "listening" : ""}" onclick="startGamepadRemap('${action}')">
                ${isListening ? "Knopf drücken..." : "Neu zuordnen"}
            </button>
        `;
        list.appendChild(row);
    }
}

function startGamepadRemap(action) {
    if (!gamepadConnected) {
        addChatMessage("🎮 Kein Controller verbunden - zum Neu-Zuordnen muss einer angeschlossen sein.", "system");
        return;
    }
    gamepadRemapListenAction = action;
    renderGamepadSettings();
}

function resetGamepadMapping() {
    GAMEPAD_BUTTON_KEY_MAP = { ...GAMEPAD_DEFAULT_BUTTON_KEY_MAP };
    localStorage.removeItem("gamepadButtonMap");
    renderGamepadSettings();
    addChatMessage("🎮 Tastenzuordnung auf Standard zurückgesetzt.", "system");
}

let shops = [];
let atms = [];
let garages = [];
let jobcenters = [];
let gasStations = [];
let clothingShops = [];
let trailerShops = [];
let currentShopIsOwner = false;
let highwayLinks = [];
let blackMarketSpots = [];
let insuranceOffices = [];
let plateOffices = [];
let repairShops = [];
let houses = [];
let jobPoints = [];
let taxiStands = [];
let hospitals = [];
let prisons = [];
let territoryZones = [];
let impoundLots = [];
let fireStations = [];
let harvestSpots = [];
let processSpots = [];
let dealerSpots = [];
let fires = [];
let groundDrops = [];
let activeEvents = { doubleXp: { active: false, endsAt: 0 }, discount: { active: false, endsAt: 0, percent: 0 } };
let naviTarget = null; // { x, y, world, label }
let jailedUntil = null;

let gameHour = 12;
let crimeAlerts = [];
let weather = "clear";
let rainDrops = [];
for (let i = 0; i < 120; i++) {
    rainDrops.push({ x: Math.random(), y: Math.random(), speed: 8 + Math.random() * 6 });
}

// -------------------------------------------------------------
// AUTOS (NEU)
// -------------------------------------------------------------
let cars = [];
let drivingCarId = null;
let passengerCarId = null;
let carConfigs = {};
const carImageCache = {};

fetch("/api/car_configs")
    .then(res => res.json())
    .then(data => {
        carConfigs = data.configs || {};
        Object.entries(carConfigs).forEach(([model, cfg]) => {
            if (cfg.image) {
                const img = new Image();
                img.src = cfg.image;
                carImageCache[model] = img;
            }
        });
        markLoadingStepDone("carConfigs", "Fahrzeuge geladen...");
    })
    .catch(() => markLoadingStepDone("carConfigs"));

let objectConfig = {};

fetch("/api/object_config")
    .then(res => res.json())
    .then(data => {
        objectConfig = data;
        markLoadingStepDone("objectConfig", "Objekte geladen...");
    })
    .catch(() => markLoadingStepDone("objectConfig"));

// Item-Infos (Typ, Schaden, Reichweite) für Client-seitige Waffen-Erkennung
let itemInfoCache = {};
fetch("/api/items")
    .then(res => res.json())
    .then(data => {
        (data.items || []).forEach(i => {
            itemInfoCache[i.id] = { name: i.name, type: i.type, damage: i.damage, weaponRange: i.weapon_range };
        });
        markLoadingStepDone("items", "Items geladen...");
    })
    .catch(() => markLoadingStepDone("items"));

let clothingCatalogCache = {}; // id -> { id, slot, name, color, price, image }
fetch("/api/clothing_catalog")
    .then(res => res.json())
    .then(data => {
        (data.items || []).forEach(i => { clothingCatalogCache[i.id] = i; });
        markLoadingStepDone("clothingCatalog", "Kleidung geladen...");
    })
    .catch(() => markLoadingStepDone("clothingCatalog"));

// -------------------------------------------------------------
// RADIO IM AUTO (NEU)
// -------------------------------------------------------------
let radioStations = [];
let currentRadioIndex = -1; // -1 = aus
const radioAudio = document.getElementById("radioAudio");

fetch("/api/radio_stations")
    .then(res => res.json())
    .then(data => {
        radioStations = data.stations || [];
        markLoadingStepDone("radioStations", "Radiosender geladen...");
    })
    .catch(() => markLoadingStepDone("radioStations"));

// -------------------------------------------------------------
// SPRACHCHAT (LiveKit, Nahbereichs-Sprachchat pro Spielwelt)
// -------------------------------------------------------------
let voiceRoom = null;
let voiceCurrentWorld = null;
let voiceMicEnabled = true;
let voiceConnecting = false;
const voiceAudioElements = {}; // Spieler-ID (als String) -> <audio>-Element
const VOICE_MAX_RANGE = 500; // px - ab hier komplett stumm, linear leiser werdend

async function connectVoiceChat(world) {
    if (typeof LivekitClient === "undefined") {
        console.warn("[Sprachchat] LiveKit-SDK nicht geladen.");
        return;
    }
    if (voiceConnecting) return;
    if (voiceRoom && voiceCurrentWorld === world) return; // schon im richtigen Raum

    voiceConnecting = true;
    await disconnectVoiceChat();

    try {
        const authToken = localStorage.getItem("token");
        const res = await fetch("/api/voice/token", { headers: { "Authorization": "Bearer " + authToken } });
        const data = await res.json();

        if (!data.ok) {
            updateVoiceDisplay("nicht eingerichtet", "#888");
            voiceConnecting = false;
            return;
        }

        const room = new LivekitClient.Room();

        room.on(LivekitClient.RoomEvent.TrackSubscribed, (track, publication, participant) => {
            if (track.kind !== "audio") return;
            const el = track.attach();
            el.autoplay = true;
            el.style.display = "none";
            document.body.appendChild(el);
            voiceAudioElements[participant.identity] = el;
        });

        room.on(LivekitClient.RoomEvent.TrackUnsubscribed, (track, publication, participant) => {
            track.detach().forEach(el => el.remove());
            delete voiceAudioElements[participant.identity];
        });

        room.on(LivekitClient.RoomEvent.Disconnected, () => {
            if (voiceRoom === room) updateVoiceDisplay("getrennt", "#888");
        });

        await room.connect(data.url, data.token);
        await room.localParticipant.setMicrophoneEnabled(voiceMicEnabled);

        voiceRoom = room;
        voiceCurrentWorld = world;
        updateVoiceDisplay(voiceMicEnabled ? "an" : "stumm", voiceMicEnabled ? "#6fbf73" : "#e0a02c");
    } catch (err) {
        console.error("[Sprachchat] Verbindung fehlgeschlagen:", err);
        updateVoiceDisplay("Fehler", "#e06c6c");
    }
    voiceConnecting = false;
}

async function disconnectVoiceChat() {
    if (voiceRoom) {
        try { await voiceRoom.disconnect(); } catch {}
    }
    voiceRoom = null;
    voiceCurrentWorld = null;
    Object.values(voiceAudioElements).forEach(el => el.remove());
    for (const key in voiceAudioElements) delete voiceAudioElements[key];
}

function toggleMic() {
    voiceMicEnabled = !voiceMicEnabled;
    if (voiceRoom) {
        voiceRoom.localParticipant.setMicrophoneEnabled(voiceMicEnabled).catch(() => {});
    }
    updateVoiceDisplay(voiceMicEnabled ? "an" : "stumm", voiceMicEnabled ? "#6fbf73" : "#e0a02c");
}

function updateVoiceDisplay(status, color) {
    const el = document.getElementById("voiceDisplay");
    if (!el) return;
    el.textContent = "🎤 " + status;
    el.style.color = color;
}

// Lautstärke jedes Mitspielers laufend nach Entfernung anpassen (Nahbereichs-Effekt)
setInterval(() => {
    if (!voiceRoom) return;

    for (const [identity, audioEl] of Object.entries(voiceAudioElements)) {
        const otherId = Number(identity);
        const other = otherPlayers.find(p => p.id === otherId);

        if (!other || other.world !== player.world) {
            audioEl.volume = 0;
            continue;
        }

        const dist = Math.hypot(other.x - player.x, other.y - player.y);
        audioEl.volume = Math.max(0, Math.min(1, 1 - dist / VOICE_MAX_RANGE));
    }
}, 200);

// -------------------------------------------------------------
// MOBILTELEFON: KONTAKTE, SMS, ANRUFE
// -------------------------------------------------------------
let phoneContacts = [];
let phonePendingRequests = [];
let currentSmsContact = null;

let incomingCallId = null;
let incomingCallerName = null;
let callRoom = null;
let activeCallInfo = null; // { otherUsername }

function openPhone() {
    document.getElementById("phoneBox").style.display = "flex";
    ws.send(JSON.stringify({ type: "phone_get_contacts" }));
}
function closePhone() {
    document.getElementById("phoneBox").style.display = "none";
}

document.querySelectorAll(".phone-tab-btn").forEach(btn => {
    btn.addEventListener("click", () => {
        document.querySelectorAll(".phone-tab-btn").forEach(b => b.classList.remove("active"));
        document.querySelectorAll(".phone-panel").forEach(p => p.classList.remove("active"));
        btn.classList.add("active");
        document.getElementById("phonePanel-" + btn.dataset.phoneTab).classList.add("active");
    });
});

function renderPhoneContacts() {
    const pendingEl = document.getElementById("pendingRequestsList");
    pendingEl.innerHTML = "";
    phonePendingRequests.forEach(username => {
        const row = document.createElement("div");
        row.className = "contact-row";
        row.innerHTML = `
            <span>📩 ${escapeHtmlAdmin(username)} möchte dich hinzufügen</span>
            <span>
                <button class="call" onclick="phoneAcceptContact('${escapeHtmlAdmin(username)}')">Annehmen</button>
            </span>
        `;
        pendingEl.appendChild(row);
    });

    const listEl = document.getElementById("contactsList");
    listEl.innerHTML = "";
    if (phoneContacts.length === 0) {
        listEl.innerHTML = `<div style="color:#666; font-size:12px; padding:8px 0;">Noch keine Kontakte.</div>`;
    }
    phoneContacts.forEach(c => {
        const row = document.createElement("div");
        row.className = "contact-row";
        row.innerHTML = `
            <span><span class="dot ${c.online ? "online" : "offline"}"></span>${escapeHtmlAdmin(c.username)}</span>
            <span>
                <button onclick="phoneOpenSmsWith('${escapeHtmlAdmin(c.username)}')">SMS</button>
                <button class="call" ${c.online ? "" : "disabled"} onclick="initiateCall('${escapeHtmlAdmin(c.username)}')">Anrufen</button>
                <button class="danger" onclick="phoneRemoveContact('${escapeHtmlAdmin(c.username)}')">Entfernen</button>
            </span>
        `;
        listEl.appendChild(row);
    });

    const select = document.getElementById("smsContactSelect");
    select.innerHTML = phoneContacts.map(c => `<option value="${escapeHtmlAdmin(c.username)}">${escapeHtmlAdmin(c.username)}</option>`).join("")
        || `<option value="">Keine Kontakte</option>`;
}

function phoneAddContact() {
    const input = document.getElementById("phoneAddInput");
    const username = input.value.trim();
    if (!username) return;
    ws.send(JSON.stringify({ type: "phone_add_contact", username }));
    input.value = "";
}
function phoneAcceptContact(username) {
    ws.send(JSON.stringify({ type: "phone_accept_contact", username }));
}
function phoneRemoveContact(username) {
    if (!confirm(`${username} wirklich aus den Kontakten entfernen?`)) return;
    ws.send(JSON.stringify({ type: "phone_remove_contact", username }));
}

function phoneOpenSmsWith(username) {
    currentSmsContact = username;
    document.querySelectorAll(".phone-tab-btn").forEach(b => b.classList.remove("active"));
    document.querySelectorAll(".phone-panel").forEach(p => p.classList.remove("active"));
    document.querySelector('.phone-tab-btn[data-phone-tab="sms"]').classList.add("active");
    document.getElementById("phonePanel-sms").classList.add("active");
    document.getElementById("smsContactSelect").value = username;
    ws.send(JSON.stringify({ type: "phone_get_messages", username }));
}

document.getElementById("smsContactSelect").addEventListener("change", e => {
    if (e.target.value) phoneOpenSmsWith(e.target.value);
});

function renderSmsThread(messages) {
    const el = document.getElementById("smsThread");
    el.innerHTML = "";
    messages.forEach(m => {
        const bubble = document.createElement("div");
        bubble.className = "sms-bubble " + (m.fromMe ? "mine" : "theirs");
        bubble.textContent = m.text;
        el.appendChild(bubble);
    });
    el.scrollTop = el.scrollHeight;
}

function phoneSendSms() {
    if (!currentSmsContact) {
        addChatMessage("Erst einen Kontakt auswählen.", "system");
        return;
    }
    const input = document.getElementById("phoneSmsInput");
    const text = input.value.trim();
    if (!text) return;
    ws.send(JSON.stringify({ type: "phone_send_sms", username: currentSmsContact, text }));
    input.value = "";
}

// -------------------------------------------------------------
// ANRUFE
// -------------------------------------------------------------
function initiateCall(username) {
    ws.send(JSON.stringify({ type: "phone_call_request", username }));
}

function showIncomingCall(callId, callerName) {
    incomingCallId = callId;
    incomingCallerName = callerName;
    document.getElementById("incomingCallText").textContent = `📞 Anruf von ${callerName}...`;
    document.getElementById("incomingCallBox").style.display = "block";
}
function hideIncomingCall() {
    incomingCallId = null;
    incomingCallerName = null;
    document.getElementById("incomingCallBox").style.display = "none";
}

function acceptIncomingCall() {
    if (incomingCallId === null) return;
    ws.send(JSON.stringify({ type: "phone_call_accept", callId: incomingCallId }));
    hideIncomingCall();
}
function declineIncomingCall() {
    if (incomingCallId === null) return;
    ws.send(JSON.stringify({ type: "phone_call_decline", callId: incomingCallId }));
    hideIncomingCall();
}

async function connectToCallRoom(data) {
    // Mikrofon im Nahbereichs-Sprachchat kurz stummschalten, damit es
    // während des Anrufs nicht doppelt in beide Räume übertragen wird
    if (voiceRoom) {
        try { await voiceRoom.localParticipant.setMicrophoneEnabled(false); } catch {}
    }

    try {
        const room = new LivekitClient.Room();

        room.on(LivekitClient.RoomEvent.TrackSubscribed, (track) => {
            if (track.kind !== "audio") return;
            const el = track.attach();
            el.id = "callAudioEl";
            el.autoplay = true;
            document.body.appendChild(el);
        });
        room.on(LivekitClient.RoomEvent.Disconnected, () => {
            teardownCallRoom(false);
        });

        await room.connect(data.url, data.token);
        await room.localParticipant.setMicrophoneEnabled(true);

        callRoom = room;
        activeCallInfo = { otherUsername: data.otherUsername };

        document.getElementById("outgoingCallBox").style.display = "none";
        document.getElementById("activeCallText").textContent = `📞 Im Gespräch mit ${data.otherUsername}`;
        document.getElementById("activeCallBar").style.display = "block";
    } catch (err) {
        console.error("[Anruf] Verbindung fehlgeschlagen:", err);
        addChatMessage("Anruf-Verbindung fehlgeschlagen.", "system");
    }
}

async function teardownCallRoom(notifyServer) {
    if (callRoom) {
        try { await callRoom.disconnect(); } catch {}
        callRoom = null;
    }
    document.getElementById("callAudioEl")?.remove();

    if (notifyServer && activeCallInfo) {
        ws.send(JSON.stringify({ type: "phone_call_end", otherUsername: activeCallInfo.otherUsername }));
    }
    activeCallInfo = null;

    document.getElementById("activeCallBar").style.display = "none";
    document.getElementById("outgoingCallBox").style.display = "none";

    // Nahbereichs-Mikro wieder freigeben, falls es vorher an war
    if (voiceRoom && voiceMicEnabled) {
        try { await voiceRoom.localParticipant.setMicrophoneEnabled(true); } catch {}
    }
}

function endCall() {
    teardownCallRoom(true);
}

// -------------------------------------------------------------
// EINSATZ-TABLET (Polizei/Sanitäter/Feuerwehr)
// -------------------------------------------------------------
function openTablet() {
    document.getElementById("tabletBox").style.display = "flex";
}
function closeTablet() {
    document.getElementById("tabletBox").style.display = "none";
}

// -------------------------------------------------------------
// SUPPORT-TICKETS
// -------------------------------------------------------------
let currentTicketId = null;
let myTicketsCache = [];

// -------------------------------------------------------------
// BÖRSE
// -------------------------------------------------------------
let stockListCache = [];
let currentStockTab = "list";

function openStockBox() {
    document.getElementById("stockBox").style.display = "flex";
    showStockTab("list");
}
function closeStockBox() {
    document.getElementById("stockBox").style.display = "none";
}

function showStockTab(tab) {
    currentStockTab = tab;
    document.getElementById("stockListPanel").classList.toggle("active", tab === "list");
    document.getElementById("stockPortfolioPanel").classList.toggle("active", tab === "portfolio");
    document.querySelectorAll("#stockTabs button").forEach((btn, i) => {
        btn.classList.toggle("active", (tab === "list" && i === 0) || (tab === "portfolio" && i === 1));
    });

    if (tab === "list") loadStockList(); else loadStockPortfolio();
}

async function loadStockList() {
    const res = await fetch("/api/stocks");
    const data = await res.json();
    if (!data.ok) return;
    stockListCache = data.stocks || [];

    const panel = document.getElementById("stockListPanel");
    panel.innerHTML = stockListCache.map(s => `
        <div class="stock-row">
            <div class="stock-info">
                <span class="stock-symbol">${escapeHtmlAdmin(s.symbol)}</span>
                <span class="stock-name">${escapeHtmlAdmin(s.name)}</span>
            </div>
            <span class="stock-price">${Number(s.price).toFixed(2)}$</span>
            <div class="stock-trade">
                <input type="number" id="stockBuyAmount_${s.id}" value="1" min="1">
                <button onclick="tradeStock(${s.id}, 'buy')">Kaufen</button>
            </div>
        </div>
    `).join("");
}

async function loadStockPortfolio() {
    const data = await ticketFetch("/api/stocks/portfolio");
    const panel = document.getElementById("stockPortfolioPanel");

    if (!data.ok || !data.portfolio || data.portfolio.length === 0) {
        panel.innerHTML = `<p style="color:#888; font-size:12px;">Du besitzt noch keine Aktien.</p>`;
        return;
    }

    panel.innerHTML = data.portfolio.map(p => {
        const value = (p.shares * Number(p.price)).toFixed(2);
        return `
            <div class="stock-row">
                <div class="stock-info">
                    <span class="stock-symbol">${escapeHtmlAdmin(p.symbol)}</span>
                    <span class="stock-name">${p.shares}x · Wert: ${value}$</span>
                </div>
                <span class="stock-price">${Number(p.price).toFixed(2)}$</span>
                <div class="stock-trade">
                    <input type="number" id="stockSellAmount_${p.stock_id}" value="1" min="1" max="${p.shares}">
                    <button class="sell" onclick="tradeStock(${p.stock_id}, 'sell')">Verkaufen</button>
                </div>
            </div>
        `;
    }).join("");
}

// -------------------------------------------------------------
// FMS-STATUS
// -------------------------------------------------------------
const FMS_STATUS_LABELS = {
    0: "Notruf",
    1: "Einsatzbereit Funk",
    2: "Einsatzbereit Wache",
    3: "Anfahrt zur Einsatzstelle",
    4: "Ankunft am Einsatzort",
    5: "Sprechwunsch",
    6: "Nicht einsatzbereit",
    7: "Einsatz erledigt, Rückfahrt",
    8: "Sprechwunsch Leitstelle"
};
let myFmsStatus = 2;

function renderFmsStatusButtons() {
    const container = document.getElementById("fmsStatusButtons");
    container.innerHTML = Object.entries(FMS_STATUS_LABELS).map(([code, label]) => `
        <div class="fms-btn ${Number(code) === myFmsStatus ? "active" : ""}" onclick="setFmsStatus(${code})">
            <span><span class="fms-code">${code}</span>${escapeHtmlAdmin(label)}</span>
        </div>
    `).join("");
}

function setFmsStatus(status) {
    myFmsStatus = status;
    ws.send(JSON.stringify({ type: "set_fms_status", status }));
    renderFmsStatusButtons();
}

// -------------------------------------------------------------
// LEITSTELLE
// -------------------------------------------------------------
const LEITSTELLE_STATUS_COLORS = {
    0: "#c0392b", 1: "#27ae60", 2: "#27ae60", 3: "#e0a83a",
    4: "#3498db", 5: "#9b59b6", 6: "#555", 7: "#e0a83a", 8: "#9b59b6"
};
const JOB_TYPE_ICONS = { police: "🚓", medic: "⛑️", fire: "🚒" };

function openLeitstelle() {
    document.getElementById("leitstelleBox").style.display = "flex";
    ws.send(JSON.stringify({ type: "leitstelle_open" }));
}
function closeLeitstelle() {
    document.getElementById("leitstelleBox").style.display = "none";
    ws.send(JSON.stringify({ type: "leitstelle_close" }));
}

function renderLeitstelleUnits(units) {
    const list = document.getElementById("leitstelleUnitsList");
    if (!units || units.length === 0) {
        list.innerHTML = `<p style="color:#888; font-size:12px;">Aktuell keine Einheiten im Dienst.</p>`;
        return;
    }

    list.innerHTML = units.map(u => `
        <div class="ls-unit-row">
            <span>${JOB_TYPE_ICONS[u.jobType] || ""} <b>${escapeHtmlAdmin(u.username)}</b> · ${escapeHtmlAdmin(u.world)}</span>
            <span class="ls-status-badge" style="background:${LEITSTELLE_STATUS_COLORS[u.fmsStatus] || "#555"};">
                ${u.fmsStatus} · ${escapeHtmlAdmin(FMS_STATUS_LABELS[u.fmsStatus] || "?")}
            </span>
        </div>
    `).join("");
}

function sendLeitstelleDispatch() {
    const jobType = document.getElementById("leitstelleTargetJob").value;
    const text = document.getElementById("leitstelleDispatchText").value.trim();
    if (!text) return;

    ws.send(JSON.stringify({ type: "leitstelle_dispatch", jobType, text }));
    document.getElementById("leitstelleDispatchText").value = "";
}

function tradeStock(stockId, action) {
    const input = document.getElementById(
        action === "buy" ? `stockBuyAmount_${stockId}` : `stockSellAmount_${stockId}`
    );
    const shares = Math.floor(Number(input.value));
    if (isNaN(shares) || shares < 1) {
        addChatMessage("Ungültige Stückzahl.", "system");
        return;
    }
    ws.send(JSON.stringify({ type: action === "buy" ? "stock_buy" : "stock_sell", stockId, shares }));
    // Server antwortet asynchron per Chat-Nachricht - Ansicht kurz danach neu laden
    setTimeout(() => showStockTab(currentStockTab), 400);
}

async function ticketFetch(url, options = {}) {
    const authToken = localStorage.getItem("token");
    options.headers = { ...(options.headers || {}), "Authorization": "Bearer " + authToken };
    const res = await fetch(url, options);
    return res.json();
}

function showTicketPanel(panelId) {
    ["ticketListPanel", "ticketNewPanel", "ticketDetailPanel"].forEach(id => {
        document.getElementById(id).classList.toggle("active", id === panelId);
    });
}

function openTicketBox() {
    document.getElementById("ticketBox").style.display = "flex";
    loadMyTickets();
}
function closeTicketBox() {
    document.getElementById("ticketBox").style.display = "none";
}

async function loadMyTickets() {
    showTicketPanel("ticketListPanel");
    const data = await ticketFetch("/api/tickets");
    if (!data.ok) return;
    myTicketsCache = data.tickets || [];
    renderTicketList();
}

function renderTicketList() {
    const body = document.getElementById("ticketListBody");
    if (myTicketsCache.length === 0) {
        body.innerHTML = `<p style="color:#888; font-size:12px;">Noch keine Tickets erstellt.</p>`;
        return;
    }

    const statusLabels = { open: "Offen", in_progress: "In Bearbeitung", closed: "Geschlossen" };
    body.innerHTML = myTicketsCache.map(t => `
        <div class="ticket-row" onclick="openTicketDetail(${t.id})">
            <div class="ticket-row-top">
                <span>${escapeHtmlAdmin(t.subject)}</span>
                <span class="ticket-status ${t.status}">${statusLabels[t.status] || t.status}</span>
            </div>
            <div class="ticket-row-meta">#${t.id} · ${escapeHtmlAdmin(t.category)} · zuletzt aktualisiert ${new Date(t.updated_at).toLocaleString("de-DE")}</div>
        </div>
    `).join("");
}

function openNewTicketPanel() {
    document.getElementById("ticketNewSubject").value = "";
    document.getElementById("ticketNewMessage").value = "";
    showTicketPanel("ticketNewPanel");
}

function backToTicketList() {
    currentTicketId = null;
    loadMyTickets();
}

async function submitNewTicket() {
    const category = document.getElementById("ticketNewCategory").value;
    const subject = document.getElementById("ticketNewSubject").value.trim();
    const message = document.getElementById("ticketNewMessage").value.trim();

    if (!subject || !message) {
        showTicketMsg("ticketNewMsg", "Betreff und Nachricht sind Pflicht.", true);
        return;
    }

    const data = await ticketFetch("/api/tickets", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ subject, category, message })
    });

    if (data.ok) {
        await loadMyTickets();
        openTicketDetail(data.id);
    } else {
        showTicketMsg("ticketNewMsg", "Fehler: " + (data.error || "unbekannt"), true);
    }
}

async function openTicketDetail(ticketId) {
    currentTicketId = ticketId;
    showTicketPanel("ticketDetailPanel");

    const data = await ticketFetch("/api/tickets/" + ticketId);
    if (!data.ok) {
        addChatMessage("Ticket konnte nicht geladen werden: " + (data.error || "unbekannt"), "system");
        backToTicketList();
        return;
    }

    const statusLabels = { open: "Offen", in_progress: "In Bearbeitung", closed: "Geschlossen" };
    document.getElementById("ticketDetailHeader").innerHTML =
        `<b>#${data.ticket.id} ${escapeHtmlAdmin(data.ticket.subject)}</b><br>Status: ${statusLabels[data.ticket.status] || data.ticket.status}`;

    const myUsername = player.username;
    const msgContainer = document.getElementById("ticketMessages");
    msgContainer.innerHTML = data.messages.map(m => `
        <div class="ticket-msg ${m.username === myUsername ? "mine" : "theirs"}">
            <div class="ticket-msg-meta">${escapeHtmlAdmin(m.username)}${m.is_admin_reply ? " (Team)" : ""} · ${new Date(m.created_at).toLocaleString("de-DE")}</div>
            ${escapeHtmlAdmin(m.message)}
        </div>
    `).join("");
    msgContainer.scrollTop = msgContainer.scrollHeight;

    const replyRow = document.getElementById("ticketReplyRow");
    replyRow.style.display = data.ticket.status === "closed" ? "none" : "flex";
}

async function submitTicketReply() {
    if (!currentTicketId) return;
    const text = document.getElementById("ticketReplyText").value.trim();
    if (!text) return;

    const data = await ticketFetch(`/api/tickets/${currentTicketId}/reply`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message: text })
    });

    if (data.ok) {
        document.getElementById("ticketReplyText").value = "";
        openTicketDetail(currentTicketId);
    } else {
        addChatMessage("Antwort fehlgeschlagen: " + (data.error || "unbekannt"), "system");
    }
}

function showTicketMsg(elId, text, isError) {
    const el = document.getElementById(elId);
    el.style.color = isError ? "#e06c6c" : "#6fbf73";
    el.textContent = text;
    setTimeout(() => { el.textContent = ""; }, 4000);
}

document.querySelectorAll(".tablet-tab-btn").forEach(btn => {
    btn.addEventListener("click", () => {
        document.querySelectorAll(".tablet-tab-btn").forEach(b => b.classList.remove("active"));
        document.querySelectorAll(".tablet-panel").forEach(p => p.classList.remove("active"));
        btn.classList.add("active");
        document.getElementById("tabletPanel-" + btn.dataset.tabletTab).classList.add("active");

        if (btn.dataset.tabletTab === "fms") renderFmsStatusButtons();
    });
});

function tabletSearchPerson() {
    const username = document.getElementById("tabletSearchInput").value.trim();
    if (!username) return;
    ws.send(JSON.stringify({ type: "tablet_search_person", username }));
}

function renderTabletSearchResult(data) {
    const el = document.getElementById("tabletSearchResult");
    if (!data.found) {
        el.innerHTML = `<div class="tablet-result-card">Kein Spieler namens "${escapeHtmlAdmin(data.username)}" gefunden.</div>`;
        return;
    }

    const wantedText = data.wantedLevel > 0
        ? `<span class="wanted">⭐ Gesucht (Level ${data.wantedLevel})</span>`
        : `<span class="clean">Nicht gesucht</span>`;

    el.innerHTML = `
        <div class="tablet-result-card">
            <b>${escapeHtmlAdmin(data.username)}</b> <span style="color:#888;">(ID: ${data.playerId})</span><br>
            Status: ${data.online ? `🟢 Online (${escapeHtmlAdmin(data.world || "?")})` : "⚫ Offline"}<br>
            Fahndung: ${wantedText}<br>
            Kopfgeld: ${data.bounty > 0 ? `💀 ${data.bounty}$` : "-"}<br>
            Leben: ${data.health}%<br>
            ${data.jailed ? '<span class="wanted">🔒 In Haft</span><br>' : ""}
            ${data.banned ? '<span class="wanted">🚫 Gebannt</span><br>' : ""}
        </div>
    `;
}

function tabletLoadWantedList() {
    ws.send(JSON.stringify({ type: "tablet_wanted_list" }));
}

function renderTabletWantedList(wanted) {
    const el = document.getElementById("tabletWantedList");
    if (!wanted || wanted.length === 0) {
        el.innerHTML = `<p style="color:#6b8b93; font-size:12px;">Aktuell ist niemand gesucht.</p>`;
        return;
    }
    el.innerHTML = "";
    wanted.forEach(w => {
        const row = document.createElement("div");
        row.className = "wanted-row";
        row.innerHTML = `
            <span>${escapeHtmlAdmin(w.username)} ${"⭐".repeat(w.wantedLevel)} (${escapeHtmlAdmin(w.world)})</span>
            <span>${w.bounty > 0 ? `💀 ${w.bounty}$` : ""}</span>
        `;
        el.appendChild(row);
    });
}

function tabletLookupPlate() {
    const plate = document.getElementById("tabletPlateInput").value.trim();
    if (!plate) return;
    ws.send(JSON.stringify({ type: "tablet_plate_lookup", plate }));
}

function renderTabletPlateResult(data) {
    const el = document.getElementById("tabletPlateResult");
    if (!data.found) {
        el.innerHTML = `<div class="tablet-result-card">Kein Fahrzeug mit Kennzeichen "${escapeHtmlAdmin(data.plate)}" gefunden.</div>`;
        return;
    }

    const wantedText = data.ownerWanted > 0
        ? `<span class="wanted">⭐ Gesucht (Level ${data.ownerWanted})</span>`
        : `<span class="clean">Nicht gesucht</span>`;

    el.innerHTML = `
        <div class="tablet-result-card">
            Kennzeichen: <b>${escapeHtmlAdmin(data.plate)}</b><br>
            Fahrzeug: ${escapeHtmlAdmin(data.model)}${data.isTrailer ? " (Anhänger)" : ""}<br>
            Halter: ${escapeHtmlAdmin(data.ownerName)}<br>
            Fahndung des Halters: ${wantedText}<br>
            Kopfgeld auf Halter: ${data.ownerBounty > 0 ? `💀 ${data.ownerBounty}$` : "-"}<br>
            Welt: ${escapeHtmlAdmin(data.world)}
        </div>
    `;
}

function cycleRadio() {
    if (radioStations.length === 0) {
        addChatMessage("Keine Radiosender eingerichtet.", "system");
        return;
    }

    currentRadioIndex++;
    if (currentRadioIndex >= radioStations.length) currentRadioIndex = -1;

    updateRadioPlayback();
}

function updateRadioPlayback() {
    const label = document.getElementById("hudRadioLabel");

    if (currentRadioIndex === -1) {
        radioAudio.pause();
        radioAudio.src = "";
        if (label) label.textContent = "📻 Aus";
        return;
    }

    const station = radioStations[currentRadioIndex];
    radioAudio.src = station.url;
    radioAudio.volume = 0.5;
    radioAudio.play().catch(() => {
        addChatMessage(`Radiosender "${station.name}" konnte nicht abgespielt werden.`, "system");
    });
    if (label) label.textContent = `📻 ${station.name}`;
}

function stopRadio() {
    currentRadioIndex = -1;
    radioAudio.pause();
    radioAudio.src = "";
}

// -------------------------------------------------------------
// Tiles laden
// -------------------------------------------------------------
const tileImageCache = {};

fetch("/api/tile_config")
    .then(res => res.json())
    .then(data => {
        tileConfig = data;
        Object.entries(tileConfig).forEach(([id, tile]) => {
            if (tile.image) {
                const img = new Image();
                img.src = tile.image;
                tileImageCache[id] = img;
            }
        });
        console.log("TileConfig geladen:", tileConfig);
        markLoadingStepDone("tileConfig", "Karten-Daten geladen...");
    })
    .catch(() => markLoadingStepDone("tileConfig"));

// -------------------------------------------------------------
// REGISTER
// -------------------------------------------------------------
regBtn.onclick = async () => {
    const username = regUser.value;
    const password = regPass.value;

    const res = await fetch("/api/register", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password })
    });

    const data = await res.json();

    if (!data.ok) {
        alert(data.error);
        return;
    }

    alert("Registrierung erfolgreich! Ein Admin muss deinen Account noch freischalten, bevor du dich einloggen kannst.");
    registerBox.style.display = "none";
    loginBox.style.display = "block";
};

// -------------------------------------------------------------
// LOGIN
// -------------------------------------------------------------
loginBtn.onclick = async () => {
    const username = loginUser.value;
    const password = loginPass.value;

    const res = await fetch("/api/login", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ username, password })
    });

    const data = await res.json();

    if (!data.ok) {
        alert(data.error);
        return;
    }

    // Token für spätere Besuche speichern (Startseite + Spiel greifen darauf zu)
    localStorage.setItem("token", data.token);
    if (data.username) localStorage.setItem("username", data.username);
    localStorage.setItem("isAdmin", data.isAdmin ? "true" : "false");

    loginBox.style.display = "none";
    canvas.style.display = "block";
    showLoadingScreen();

    startMultiplayer(data.token);
};

// -------------------------------------------------------------
// UI SWITCH
// -------------------------------------------------------------
switchToRegister.onclick = () => {
    loginBox.style.display = "none";
    registerBox.style.display = "block";
    canvas.style.display = "none";
};

switchToLogin.onclick = () => {
    registerBox.style.display = "none";
    loginBox.style.display = "block";
    canvas.style.display = "none";
};

// -------------------------------------------------------------
// MULTIPLAYER STARTEN
// -------------------------------------------------------------
function startMultiplayer(token) {
    ws = new WebSocket("wss://server.borderville.de");

    ws.onopen = () => {
        ws.send(JSON.stringify({ type: "auth", token }));
    };

    ws.onclose = () => {
        if (!loggedIn) {
            // Verbindung/Login fehlgeschlagen (z.B. abgelaufener/ungültiger Token)
            // -> gespeicherten Token verwerfen und zur Startseite weiterleiten
            // (Login gibt es nur noch dort, nicht mehr auf der Spielseite)
            localStorage.removeItem("token");
            localStorage.removeItem("isAdmin");
            localStorage.removeItem("username");
            location.href = "/index.html?loginfailed=1";
        } else {
            // Verbindung wurde mitten im Spiel verloren (Server neu gestartet,
            // Netzwerkproblem, Sperrung etc.) -> zurück zur Startseite
            location.href = "/index.html?disconnected=1";
        }
    };

    ws.onmessage = ev => {
        const msg = JSON.parse(ev.data);

        if (msg.type === "auth_error") {
            alert(msg.msg || "Login fehlgeschlagen.");
        }

        if (msg.type === "auth_ok") {
            player.id = msg.id;
            player.username = msg.username;
            player.isAdmin = !!msg.isAdmin;
            maps = msg.maps;
            document.getElementById("topMenu").style.display = "flex";
            document.getElementById("statusWindow").style.display = "block";
            document.getElementById("inventoryWindow").style.display = "block";
            document.getElementById("chatBox").style.display = "flex";

            player.inventory = msg.inventory || [];
            document.getElementById("inventoryWindow").style.display = "block";
            renderInventoryWindow();
            canvas.style.display = "block";

            console.log("MAPS GELADEN:", maps);

            const map = maps[player.world];
            if (map && map.spawn) {
                player.x = map.spawn.x;
                player.y = map.spawn.y;
            }
            loggedIn = true;
            markLoadingStepDone("auth", "Anmeldung erfolgreich...");

            return;
        }
        if (msg.type === "state") {
            lastServerUpdate = Date.now();

            const me = msg.players.find(p => p.id === player.id);
            if (me) {
                // nur setzen, wenn wir NICHT gerade bewegen (und nicht fahren)
                if (passengerCarId || (!drivingCarId && !keys["w"] && !keys["a"] && !keys["s"] && !keys["d"] && !touchMove.w && !touchMove.a && !touchMove.s && !touchMove.d)) {
                    player.x = me.x;
                    player.y = me.y;
                }
                if (voiceCurrentWorld !== me.world) {
                    connectVoiceChat(me.world); // Welt gewechselt (oder Erstverbindung) -> Sprachchat-Raum wechseln
                }
                player.world = me.world;
                player.money = me.money;
                player.bank = me.bank;
                player.health = me.health;
                player.hunger = me.hunger;
                player.thirst = me.thirst;
                player.inventory = me.inventory;
                player.hasPackage = me.hasPackage;

                if (!playerStateLoaded) {
                    playerStateLoaded = true;
                    markLoadingStepDone("playerState", "Spielerdaten geladen...");
                }

                player.color = me.color || "#f1c40f";
                player.skin = me.skin || "none";
                player.wantedLevel = me.wantedLevel || 0;
                player.cuffed = !!me.cuffed;
                player.inSafeZone = !!me.inSafeZone;
                player.shirtColor = me.shirtColor;
                player.pantsColor = me.pantsColor;
                player.shoesColor = me.shoesColor;
                player.shirtImage = me.shirtImage;
                player.pantsImage = me.pantsImage;
                player.shoesImage = me.shoesImage;
                player.skinImage = me.skinImage;
                player.helmetImage = me.helmetImage;
                player.title = me.title || null;
                player.jobType = me.jobType || null;
                player.jobName = me.jobName || null;
                const oldLevel = player.level;
                player.xp = me.xp || 0;
                player.level = me.level || 1;
                if (oldLevel !== undefined && player.level > oldLevel) {
                    // Level-Up-Glow kurz anzeigen
                    const el = document.getElementById("levelDisplay");
                    if (el) {
                        el.classList.add("level-up-flash");
                        setTimeout(() => el.classList.remove("level-up-flash"), 1500);
                    }
                }
                updateLevelDisplay();
                player.gangTag = me.gangTag || null;
                player.gangColor = me.gangColor || null;
                updatePvpDisplay();
                updateWantedDisplay();
            }

            otherPlayers = msg.players.filter(p => p.id !== player.id);
            updateOnlineCount(msg.players.length);
            renderInventoryWindow();
        }

        // AUTOS (NEU)
        if (msg.type === "cars") {
            cars = msg.cars;
            const mine = cars.find(c => c.driverId === player.id);
            const wasDriving = !!drivingCarId;
            drivingCarId = mine ? mine.id : null;
            if (mine) { player.x = mine.x; player.y = mine.y; }
            if (wasDriving && !drivingCarId) stopRadio();

            const myPassengerCar = cars.find(c => c.passengerId === player.id);
            passengerCarId = myPassengerCar ? myPassengerCar.id : null;
            if (myPassengerCar) { player.x = myPassengerCar.x; player.y = myPassengerCar.y; }

            updateCarHud(mine || myPassengerCar);
        }
        if (msg.type === "shop_info") {
            addChatMessage(msg.msg, "system");
        }
        if (msg.type === "garage_open") {
            openGarageWindow(msg.cars);
        }
        if (msg.type === "garage_info") {
            addChatMessage(msg.msg, "system");
        }
        if (msg.type === "jobcenter_open") {
            openJobcenterWindow(msg.jobs, msg.currentRankId);
        }
        if (msg.type === "jobcenter_info") {
            addChatMessage(msg.msg, "system");
        }
        if (msg.type === "world_info") {
            gameHour = msg.hour;
            weather = msg.weather;
            updateClockDisplay();
        }
        if (msg.type === "crime_alert") {
            crimeAlerts.push({
                world: msg.world,
                x: msg.x,
                y: msg.y,
                kind: "crime",
                expiresAt: Date.now() + 60000
            });
            addChatMessage(`🚨 ${msg.crimeType} gemeldet in "${msg.world}" bei (${msg.x}, ${msg.y})!`, "system");
        }
        if (msg.type === "server_restart_now") {
            addChatMessage("🔄 Server wird jetzt neu gestartet - du wirst zur Startseite weitergeleitet...", "system");
            setTimeout(() => {
                location.href = "/index.html?restarted=1";
            }, 1200);
        }
        if (msg.type === "player_died") {
            showDeathScreen(msg.killerName);
        }
        if (msg.type === "jail_status") {
            jailedUntil = msg.jailedUntil;
            updateJailDisplay();
        }
        if (msg.type === "admin_duty_status") {
            player.adminDuty = msg.onDuty;
            updateDutyDisplay();
        }
        if (msg.type === "medic_alert") {
            crimeAlerts.push({
                world: msg.world,
                x: msg.x,
                y: msg.y,
                kind: "medic",
                expiresAt: Date.now() + 60000
            });
            addChatMessage(`⛑️ ${msg.crimeType} gemeldet in "${msg.world}" bei (${msg.x}, ${msg.y})!`, "system");
        }
        if (msg.type === "fire_update") {
            fires = msg.fires || [];
        }
        if (msg.type === "ground_drop_update") {
            groundDrops = msg.drops || [];
        }
        if (msg.type === "dealer_update") {
            dealerSpots = msg.spots || [];
        }
        if (msg.type === "fire_alert") {
            addChatMessage(`🔥 Feuer gemeldet in "${msg.world}" bei (${msg.x}, ${msg.y})!`, "system");
        }
        if (msg.type === "event_update") {
            activeEvents = msg.events;
            updateEventDisplay();
        }
        if (msg.type === "radio_message") {
            addChatMessage(`${msg.icon} [${msg.jobName}-Funk] ${msg.username}: ${msg.text}`, "radio");
        }

        // -------------------------------------------------------
        // HANDY: KONTAKTE / SMS
        // -------------------------------------------------------
        if (msg.type === "phone_contacts_data") {
            phoneContacts = msg.contacts || [];
            phonePendingRequests = msg.pendingRequests || [];
            renderPhoneContacts();
        }
        if (msg.type === "phone_sms_sent") {
            if (currentSmsContact === msg.username) {
                ws.send(JSON.stringify({ type: "phone_get_messages", username: msg.username }));
            }
        }
        if (msg.type === "phone_sms_received") {
            addChatMessage(`📩 SMS von ${msg.username}: ${msg.text}`, "system");
            if (currentSmsContact === msg.username) {
                ws.send(JSON.stringify({ type: "phone_get_messages", username: msg.username }));
            }
        }
        if (msg.type === "phone_messages_data") {
            if (currentSmsContact === msg.username) {
                renderSmsThread(msg.messages || []);
            }
        }

        // -------------------------------------------------------
        // HANDY: ANRUFE
        // -------------------------------------------------------
        if (msg.type === "incoming_call") {
            showIncomingCall(msg.callId, msg.callerName);
        }
        if (msg.type === "call_ringing") {
            document.getElementById("outgoingCallText").textContent = `📞 Rufe ${msg.username} an...`;
            document.getElementById("outgoingCallBox").style.display = "block";
        }
        if (msg.type === "call_connected") {
            connectToCallRoom(msg);
        }
        if (msg.type === "call_declined") {
            document.getElementById("outgoingCallBox").style.display = "none";
            addChatMessage("Anruf wurde abgelehnt.", "system");
        }
        if (msg.type === "call_missed") {
            document.getElementById("outgoingCallBox").style.display = "none";
            addChatMessage(`Verpasster Anruf bei ${msg.username} (keine Antwort).`, "system");
        }
        if (msg.type === "call_cancelled") {
            hideIncomingCall();
            document.getElementById("outgoingCallBox").style.display = "none";
        }
        if (msg.type === "call_ended") {
            teardownCallRoom(false);
            addChatMessage("Anruf beendet.", "system");
        }

        if (msg.type === "clothing_shop_open") {
            openClothingShop(msg.catalog);
        }

        if (msg.type === "trailer_shop_open") {
            openTrailerShop(msg.catalog);
        }

        if (msg.type === "close_window") {
            const el = document.getElementById(msg.windowType);
            if (el) el.style.display = "none";
            if (msg.reason === "cuffed") {
                addChatMessage("🔗 Fenster geschlossen - du wurdest gefesselt.", "system");
            }
        }

        if (msg.type === "leitstelle_units") {
            renderLeitstelleUnits(msg.units);
        }

        if (msg.type === "ticket_update") {
            if (msg.eventType === "ticket_reply" && msg.ticketId === currentTicketId) {
                openTicketDetail(currentTicketId); // aktuell offenes Ticket live aktualisieren
            } else {
                addChatMessage("🎫 Ein Ticket wurde aktualisiert.", "system");
            }
        }

        if (msg.type === "tablet_person_result") {
            renderTabletSearchResult(msg);
        }
        if (msg.type === "tablet_wanted_list_result") {
            renderTabletWantedList(msg.wanted);
        }
        if (msg.type === "tablet_plate_result") {
            renderTabletPlateResult(msg);
        }

        if (msg.type === "zone_update") {
            territoryZones = msg.zones || [];
        }
        if (msg.type === "gate_toggle") {
            const map = maps[player.world];
            if (map && map.objects && map.objects[msg.objectIndex]) {
                map.objects[msg.objectIndex].open = msg.open;
            }
        }
        if (msg.type === "taxi_requests") {
            openTaxiWindow(msg.requests);
        }
        if (msg.type === "job_menu_data") {
            renderJobMenu(msg);
        }
        if (msg.type === "tune_menu_data") {
            openTuneWindow(msg);
        }
        if (msg.type === "admin_players") {
            renderAdminPlayerList(msg.players);
        }
        if (msg.type === "jobcenter_error") {
            addChatMessage(msg.msg, "system");
        }
        if (msg.type === "house_open") {
            openHouseWindow(msg);
        }
        if (msg.type === "wardrobe_open") {
            openWardrobeWindow(msg);
        }
        if (msg.type === "trunk_open") {
            openTrunkWindow(msg.carId, msg.trunk, msg.inventory);
        }
        if (msg.type === "trunk_error") {
            addChatMessage(msg.msg, "system");
        }

        if (msg.type === "npc_dialog") {
            addChatMessage("NPC: " + msg.text, "system");
        }
        if (msg.type === "shop_open") {
            const shopWindow = document.getElementById("shopWindow");
            const shopList = document.getElementById("shopList");

            shopWindow.style.display = "block";
            shopList.innerHTML = "";
            currentShopIsOwner = !!msg.isOwner;

            if (msg.isOwner) {
                const hint = document.createElement("div");
                hint.style.cssText = "padding:6px; margin-bottom:8px; color:#7fb0ec; font-size:12px;";
                hint.textContent = "🏪 Das ist dein Shop - du kannst die Preise deiner Artikel selbst festlegen.";
                shopList.appendChild(hint);
            } else if (!msg.ownerId && msg.purchasePrice) {
                const hint = document.createElement("div");
                hint.style.cssText = "padding:6px; margin-bottom:8px; color:#e0a83a; font-size:12px;";
                hint.innerHTML = `🏪 Dieser Shop ist unverkauft - kostet <b>${msg.purchasePrice}$</b> (<code>/shop buy</code>).`;
                shopList.appendChild(hint);
            }

            msg.items.forEach(item => {
                const div = document.createElement("div");
                div.style.padding = "5px";
                div.style.borderBottom = "1px solid #333";

                if (currentShopIsOwner) {
                    div.innerHTML = `
                        ${item.name} -
                        <input type="number" id="shopPriceInput_${item.id}" value="${item.price}" min="1" max="50000" style="width:80px;">$
                        <button onclick="setShopItemPrice('${item.id}')">Preis speichern</button>
                    `;
                } else {
                    div.innerHTML = `
                        ${item.name} - ${item.price}$
                        <button onclick="buyItem('${item.id}')">Kaufen</button>
                    `;
                }
                shopList.appendChild(div);
            });
        }

        if (msg.type === "shop_price_updated") {
            addChatMessage(`🏪 Preis aktualisiert: ${msg.price}$`, "system");
        }

        if (msg.type === "shop_error") {
            addChatMessage(msg.msg, "system");
        }
        if (msg.type === "item_used") {
            player.hunger = msg.hunger;
            player.thirst = msg.thirst;
            player.health = msg.health;
            player.inventory = msg.inventory;

            updateStatusWindow();
            renderInventoryWindow();
        }
        if (msg.type === "atm_open") {
            const pin = prompt("PIN eingeben");
            ws.send(JSON.stringify({ type: "bank_pin_check", pin }));
        }
        if (msg.type === "bank_pin_ok") {
            ws.send(JSON.stringify({ type: "bank_open" }));
        }
        if (msg.type === "bank_pin_fail") {
            addChatMessage("Falsche PIN!", "system");
        }
        if (msg.type === "bank_pin_set_result") {
            const el = document.getElementById("atmPinMsg");
            if (el) {
                el.textContent = msg.msg;
                el.style.color = msg.ok ? "#6fbf73" : "#e06c6c";
            }
            if (msg.ok) {
                document.getElementById("atmOldPin").value = "";
                document.getElementById("atmNewPin").value = "";
            }
        }
        if (msg.type === "bank_open") {
            openATMWindow(msg.money, msg.bank);
        }
        if (msg.type === "bank_update") {
            document.getElementById("atmMoney").innerText = msg.money;
            document.getElementById("atmBank").innerText = msg.bank;
        }

        if (msg.type === "object_interact") {
            if (msg.action === "loot") {
                addChatMessage("Du hast die Kiste geöffnet!", "system");
            }
        }

        if (msg.type === "chat_message") {
            if (msg.system) {
                addChatMessage(msg.text, "system");
            } else {
                addChatMessage(msg.text, "player", msg.from);
            }
        }

        // Map-Daten
        if (msg.type === "map_data") {
            tiles = msg.tiles;
            doors = msg.doors || [];
            objects = msg.objects || [];
            shops = msg.shops || [];
            garages = msg.garages || [];
            jobcenters = msg.jobcenters || [];
            gasStations = msg.gasStations || [];
            clothingShops = msg.clothingShops || [];
            trailerShops = msg.trailerShops || [];
            highwayLinks = msg.highwayLinks || [];
            blackMarketSpots = msg.blackMarketSpots || [];
            insuranceOffices = msg.insuranceOffices || [];
            plateOffices = msg.plateOffices || [];
            repairShops = msg.repairShops || [];
            houses = msg.houses || [];
            taxiStands = msg.taxiStands || [];
            hospitals = msg.hospitals || [];
            prisons = msg.prisons || [];
            territoryZones = msg.territoryZones || [];
            impoundLots = msg.impoundLots || [];
            fireStations = msg.fireStations || [];
            harvestSpots = msg.harvestSpots || [];
            processSpots = msg.processSpots || [];
            dealerSpots = msg.dealerSpots || [];
            fires = msg.fires || [];
            groundDrops = msg.groundDrops || [];
            jobPoints = msg.jobPoints || [];
            atms = msg.atms || [];
            spawn = msg.spawn;

            // WICHTIG: maps[player.world] mitpflegen - renderMap(), Kollisionsprüfung,
            // Tür-/Tor-Erkennung usw. lesen ihre Daten direkt von dort, nicht von den
            // obigen Einzelvariablen. Ohne das hier würden neue/aktualisierte Welten
            // (z.B. Hausinneres, frisch im Editor gespeicherte Karte) nicht sichtbar.
            maps[player.world] = {
                tiles: msg.tiles,
                tileRot: msg.tileRot || null,
                doors: msg.doors || [],
                objects: msg.objects || [],
                atms: msg.atms || [],
                spawn: msg.spawn
            };

            return;
        }
    };
}

function openATMWindow(money, bank) {
    document.getElementById("atmMoney").innerText = money;
    document.getElementById("atmBank").innerText = bank;
    document.getElementById("atmBox").style.display = "block";
}

function closeATM() {
    document.getElementById("atmBox").style.display = "none";
}

function atmDeposit() {
    const amount = Number(document.getElementById("atmAmount").value);
    ws.send(JSON.stringify({ type: "bank_deposit", amount }));
}

function atmWithdraw() {
    const amount = Number(document.getElementById("atmAmount").value);
    ws.send(JSON.stringify({ type: "bank_withdraw", amount }));
}

function atmTransfer() {
    const amount = Number(document.getElementById("atmAmount").value);
    const targetId = Number(document.getElementById("atmTarget").value);
    ws.send(JSON.stringify({ type: "bank_transfer", amount, targetId }));
}

function atmChangePin() {
    const oldPin = document.getElementById("atmOldPin").value.trim();
    const newPin = document.getElementById("atmNewPin").value.trim();

    if (!/^\d{4}$/.test(newPin)) {
        document.getElementById("atmPinMsg").textContent = "Neue PIN muss genau 4 Ziffern haben.";
        document.getElementById("atmPinMsg").style.color = "#e06c6c";
        return;
    }

    ws.send(JSON.stringify({ type: "bank_set_pin", oldPin, newPin }));
}

// -------------------------------------------------------------
// GARAGE (NEU)
// -------------------------------------------------------------
function openGarageWindow(carList) {
    const box = document.getElementById("garageBox");
    const list = document.getElementById("garageList");
    list.innerHTML = "";

    carList.forEach(c => {
        const div = document.createElement("div");
        div.style.padding = "5px";
        div.style.borderBottom = "1px solid #333";
        div.innerHTML = `
            ${c.model} (${c.is_stored ? "eingelagert" : "unterwegs"})
            ${c.is_stored
                ? `<button class="garageRetrieveBtn" data-id="${c.id}">Herausholen</button>`
                : `<button class="garageStoreBtn" data-id="${c.id}">Einlagern</button>`}
        `;
        list.appendChild(div);
    });

    // Event-Delegation (wie beim Inventar) statt Klick-Handler pro Button
    if (!list.dataset.bound) {
        list.addEventListener("click", e => {
            const storeBtn = e.target.closest(".garageStoreBtn");
            const retrieveBtn = e.target.closest(".garageRetrieveBtn");

            if (storeBtn) {
                ws.send(JSON.stringify({ type: "garage_store", carId: Number(storeBtn.dataset.id) }));
            }
            if (retrieveBtn) {
                ws.send(JSON.stringify({ type: "garage_retrieve", carId: Number(retrieveBtn.dataset.id) }));
            }
        });
        list.dataset.bound = "true";
    }

    box.style.display = "block";
}

function closeGarage() {
    document.getElementById("garageBox").style.display = "none";
}

// -------------------------------------------------------------
// AUTO-HUD: Tacho + Tankanzeige (NEU)
// -------------------------------------------------------------
function updateCarHud(myCar) {
    const hud = document.getElementById("carHud");
    if (!hud) return;

    if (!myCar) {
        hud.style.display = "none";
        return;
    }

    hud.style.display = "block";
    document.getElementById("hudSpeed").textContent = myCar.speedKmh ?? 0;

    const pct = myCar.tankSize > 0 ? Math.max(0, Math.min(100, (myCar.fuel / myCar.tankSize) * 100)) : 0;
    document.getElementById("hudFuelBarInner").style.width = pct + "%";
    document.getElementById("hudFuelBarInner").style.background =
        pct > 30 ? "#4caf50" : (pct > 12 ? "#e0a02c" : "#c0392b");
    document.getElementById("hudFuelText").textContent =
        `${myCar.fuel.toFixed(1)} / ${myCar.tankSize}L`;

    const healthPct = Math.max(0, Math.min(100, myCar.health ?? 100));
    document.getElementById("hudHealthBarInner").style.width = healthPct + "%";
    document.getElementById("hudHealthBarInner").style.background =
        healthPct > 50 ? "#4caf50" : (healthPct > 20 ? "#e0a02c" : "#c0392b");
    document.getElementById("hudHealthText").textContent = Math.round(healthPct) + "%";

    const odoEl = document.getElementById("hudOdometer");
    if (odoEl) odoEl.textContent = (myCar.odometer ?? 0).toFixed(1) + " km";

    const insEl = document.getElementById("hudInsurance");
    if (insEl) insEl.textContent = myCar.insured ? "🛡️ Versichert" : "⚠️ Unversichert";
}

// -------------------------------------------------------------
// JOBCENTER (NEU)
// -------------------------------------------------------------
function openJobcenterWindow(jobList, currentRankId) {
    const box = document.getElementById("jobBox");
    const list = document.getElementById("jobList");
    list.innerHTML = "";

    jobList.forEach(job => {
        const jobDiv = document.createElement("div");
        jobDiv.style.marginBottom = "10px";

        const ranksHtml = job.ranks.map(r => {
            const isCurrent = r.id === currentRankId;
            const isEntry = r.level === 1;
            let btn = "";
            if (isCurrent) {
                btn = `<span style="color:#6fbf73;">aktueller Job</span>`;
            } else if (isEntry) {
                btn = `<button class="jobApplyBtn" data-id="${r.id}">Bewerben</button>`;
            } else {
                btn = `<span style="color:#666;">nur per Beförderung</span>`;
            }
            return `
                <div style="display:flex; justify-content:space-between; padding:3px 0;">
                    <span>Rang ${r.level}: ${r.title} (${r.salary}$/Min)</span>
                    ${btn}
                </div>
            `;
        }).join("");

        jobDiv.innerHTML = `
            <div style="font-weight:bold; border-bottom:1px solid #333; margin-bottom:4px;">${job.name}</div>
            ${ranksHtml}
        `;
        list.appendChild(jobDiv);
    });

    if (currentRankId) {
        const quitDiv = document.createElement("div");
        quitDiv.style.marginTop = "10px";
        quitDiv.innerHTML = `<button id="jobQuitBtn" style="background:#a83232; width:100%;">Job kündigen</button>`;
        list.appendChild(quitDiv);
    }

    // Event-Delegation (wie bei Inventar/Garage) statt Handler pro Button
    if (!list.dataset.bound) {
        list.addEventListener("click", e => {
            const applyBtn = e.target.closest(".jobApplyBtn");
            const quitBtn = e.target.closest("#jobQuitBtn");

            if (applyBtn) {
                ws.send(JSON.stringify({ type: "job_apply", rankId: Number(applyBtn.dataset.id) }));
            }
            if (quitBtn) {
                if (confirm("Job wirklich kündigen?")) {
                    ws.send(JSON.stringify({ type: "job_quit" }));
                }
            }
        });
        list.dataset.bound = "true";
    }

    box.style.display = "block";
}

function closeJobcenter() {
    document.getElementById("jobBox").style.display = "none";
}

// -------------------------------------------------------------
// KOFFERRAUM (NEU)
// -------------------------------------------------------------
let currentTrunkCarId = null;

function openTrunkWindow(carId, trunk, inventory) {
    currentTrunkCarId = carId;
    player.inventory = inventory || player.inventory;

    const box = document.getElementById("trunkBox");
    const invList = document.getElementById("trunkInventoryList");
    const trunkList = document.getElementById("trunkContentList");

    invList.innerHTML = "";
    (inventory || []).forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${item.name || item.id} x${item.amount}</span>
            <button class="trunkStoreBtn" data-id="${item.id}">→ Kofferraum</button>
        `;
        invList.appendChild(div);
    });

    trunkList.innerHTML = "";
    (trunk || []).forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${item.name || item.id} x${item.amount}</span>
            <button class="trunkTakeBtn" data-id="${item.id}">→ Inventar</button>
        `;
        trunkList.appendChild(div);
    });

    if (!box.dataset.bound) {
        box.addEventListener("click", e => {
            const storeBtn = e.target.closest(".trunkStoreBtn");
            const takeBtn = e.target.closest(".trunkTakeBtn");

            if (storeBtn) {
                ws.send(JSON.stringify({ type: "trunk_store", itemId: storeBtn.dataset.id, amount: 1 }));
            }
            if (takeBtn) {
                ws.send(JSON.stringify({ type: "trunk_take", itemId: takeBtn.dataset.id, amount: 1 }));
            }
        });
        box.dataset.bound = "true";
    }

    box.style.display = "block";
}

function closeTrunk() {
    document.getElementById("trunkBox").style.display = "none";
    currentTrunkCarId = null;
}

// -------------------------------------------------------------
// HÄUSER (NEU)
// -------------------------------------------------------------
let currentHouseId = null;

function openHouseWindow(msg) {
    currentHouseId = msg.houseId;

    const box = document.getElementById("houseBox");
    document.getElementById("houseName").textContent = msg.name;

    const buySection = document.getElementById("houseBuySection");
    const storageSection = document.getElementById("houseStorageSection");
    const lockedSection = document.getElementById("houseLockedSection");
    const forRentSection = document.getElementById("houseForRentSection");

    buySection.classList.add("hidden");
    storageSection.classList.add("hidden");
    lockedSection.classList.add("hidden");
    forRentSection.classList.add("hidden");

    if (!msg.owned) {
        buySection.classList.remove("hidden");
        document.getElementById("housePrice").textContent = msg.price + "$";
    } else if (msg.hasKey) {
        storageSection.classList.remove("hidden");
        renderHouseStorage(msg.storage || []);
    } else if (msg.forRent) {
        forRentSection.classList.remove("hidden");
        document.getElementById("houseRentPrice").textContent = msg.rentPrice + "$";
    } else {
        lockedSection.classList.remove("hidden");
    }

    if (!box.dataset.bound) {
        box.addEventListener("click", e => {
            const storeBtn = e.target.closest(".houseStoreBtn");
            const takeBtn = e.target.closest(".houseTakeBtn");

            if (storeBtn) {
                ws.send(JSON.stringify({ type: "house_store", itemId: storeBtn.dataset.id, amount: 1 }));
            }
            if (takeBtn) {
                ws.send(JSON.stringify({ type: "house_take", itemId: takeBtn.dataset.id, amount: 1 }));
            }
        });
        box.dataset.bound = "true";
    }

    box.style.display = "block";
}

function renderHouseStorage(storage) {
    const invList = document.getElementById("houseInventoryList");
    const stoList = document.getElementById("houseStorageList");

    invList.innerHTML = "";
    (player.inventory || []).forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${item.name || item.id} x${item.amount}</span>
            <button class="houseStoreBtn" data-id="${item.id}">→ Haus</button>
        `;
        invList.appendChild(div);
    });

    stoList.innerHTML = "";
    storage.forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${item.name || item.id} x${item.amount}</span>
            <button class="houseTakeBtn" data-id="${item.id}">→ Inventar</button>
        `;
        stoList.appendChild(div);
    });
}

// -------------------------------------------------------------
// KLEIDERSCHRANK (eigenständig, getrennt von der Haus-Lagerkiste)
// -------------------------------------------------------------
function itemDisplayName(item) {
    const clothingMatch = String(item.id).match(/^clothing_(\d+)$/);
    if (clothingMatch) {
        const catalogItem = clothingCatalogCache[Number(clothingMatch[1])];
        return catalogItem ? ("👕 " + catalogItem.name) : item.id;
    }
    return item.name || item.id;
}

function openWardrobeWindow(msg) {
    const box = document.getElementById("wardrobeBox");
    renderWardrobe(msg.wardrobe || []);

    if (!box.dataset.bound) {
        box.addEventListener("click", e => {
            const storeBtn = e.target.closest(".wardrobeStoreBtn");
            const takeBtn = e.target.closest(".wardrobeTakeBtn");

            if (storeBtn) {
                ws.send(JSON.stringify({ type: "wardrobe_store", itemId: storeBtn.dataset.id, amount: 1 }));
            }
            if (takeBtn) {
                ws.send(JSON.stringify({ type: "wardrobe_take", itemId: takeBtn.dataset.id, amount: 1 }));
            }
        });
        box.dataset.bound = "true";
    }

    box.style.display = "block";
}
function closeWardrobeWindow() {
    document.getElementById("wardrobeBox").style.display = "none";
}

function renderWardrobe(wardrobe) {
    const invList = document.getElementById("wardrobeInventoryList");
    const wList = document.getElementById("wardrobeStorageList");

    invList.innerHTML = "";
    (player.inventory || []).forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${escapeHtmlAdmin(itemDisplayName(item))} x${item.amount}</span>
            <button class="wardrobeStoreBtn" data-id="${item.id}">→ Schrank</button>
        `;
        invList.appendChild(div);
    });

    wList.innerHTML = "";
    wardrobe.forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "3px 0";
        div.innerHTML = `
            <span>${escapeHtmlAdmin(itemDisplayName(item))} x${item.amount}</span>
            <button class="wardrobeTakeBtn" data-id="${item.id}">→ Inventar</button>
        `;
        wList.appendChild(div);
    });
}

function buyCurrentHouse() {
    if (currentHouseId === null) return;
    ws.send(JSON.stringify({ type: "house_buy", houseId: currentHouseId }));
}

function rentCurrentHouse() {
    ws.send(JSON.stringify({ type: "house_rent" }));
    document.getElementById("houseBox").style.display = "none";
}

function closeHouse() {
    document.getElementById("houseBox").style.display = "none";
    currentHouseId = null;
}

// -------------------------------------------------------------
// IN-GAME ADMIN-MENÜ (NEU)
// -------------------------------------------------------------
function openAdminMenu() {
    const box = document.getElementById("adminBox");
    box.style.display = "block";
    ws.send(JSON.stringify({ type: "admin_get_players" }));
}

// -------------------------------------------------------------
// WELTKARTE + NAVI (NEU) - rein clientseitig, Daten sind schon geladen
// -------------------------------------------------------------
function openWorldMap() {
    const map = maps[player.world];
    if (!map || !map.tiles) return;

    const box = document.getElementById("worldMapBox");
    const canvas = document.getElementById("worldMapCanvas");
    box.style.display = "flex";

    const cols = map.tiles[0].length;
    const rows = map.tiles.length;
    const scale = Math.min(4, Math.floor(700 / Math.max(cols, rows)));
    const px = Math.max(1, scale);

    canvas.width = cols * px;
    canvas.height = rows * px;
    const ctx = canvas.getContext("2d");

    // Tiles als kleines "Foto" zeichnen
    for (let y = 0; y < rows; y++) {
        for (let x = 0; x < cols; x++) {
            const tile = tileConfig[map.tiles[y][x]];
            ctx.fillStyle = tile ? tile.color : "#000";
            ctx.fillRect(x * px, y * px, px, px);
        }
    }

    // POIs sammeln und einzeichnen
    const poiGroups = [
        { list: shops || [], color: "#f1c40f", icon: "🛒", label: "Shop" },
        { list: garages || [], color: "#2ecc71", icon: "🚗", label: "Garage" },
        { list: gasStations || [], color: "#e67e22", icon: "⛽", label: "Tankstelle" },
        { list: repairShops || [], color: "#95a5a6", icon: "🔧", label: "Werkstatt" },
        { list: jobcenters || [], color: "#9b59b6", icon: "💼", label: "Jobcenter" },
        { list: houses || [], color: "#8b5a2b", icon: "🏠", label: "Haus" },
        { list: taxiStands || [], color: "#f5d90a", icon: "🚕", label: "Taxi-Stand" },
        { list: hospitals || [], color: "#e74c3c", icon: "🏥", label: "Krankenhaus" },
        { list: prisons || [], color: "#555555", icon: "🔒", label: "Gefängnis" },
        { list: impoundLots || [], color: "#b8860b", icon: "🚛", label: "Abschlepphof" },
        { list: fireStations || [], color: "#c0392b", icon: "🚒", label: "Feuerwache" },
        { list: harvestSpots || [], color: "#2ecc71", icon: "🌿", label: "Anbaustelle" },
        { list: processSpots || [], color: "#8e44ad", icon: "⚗️", label: "Labor" },
        { list: dealerSpots || [], color: "#e67e22", icon: "💰", label: "Verkaufsort" },
        { list: clothingShops || [], color: "#e84393", icon: "👕", label: "Kleidungsladen" },
        { list: trailerShops || [], color: "#8e6a3d", icon: "🚛", label: "Anhänger-Shop" },
        { list: highwayLinks || [], color: "#f0c419", icon: "🛣️", label: "Autobahn-Anschluss" },
        { list: blackMarketSpots || [], color: "#5a1a4a", icon: "🕶️", label: "Hehler" },
        { list: insuranceOffices || [], color: "#0984e3", icon: "🛡️", label: "Versicherung" },
        { list: plateOffices || [], color: "#636e72", icon: "🔖", label: "Zulassungsstelle" }
    ];

    const markers = [];
    poiGroups.forEach(group => {
        group.list.forEach(poi => {
            const mx = poi.x * px + px / 2;
            const my = poi.y * px + px / 2;
            ctx.fillStyle = group.color;
            ctx.beginPath();
            ctx.arc(mx, my, Math.max(3, px * 0.4), 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = "black";
            ctx.lineWidth = 1;
            ctx.stroke();
            markers.push({ x: mx, y: my, worldX: poi.x * 32 + 16, worldY: poi.y * 32 + 16, label: `${group.icon} ${poi.name || group.label}` });
        });
    });

    // eigene Position markieren
    const myX = (player.x / 32) * px;
    const myY = (player.y / 32) * px;
    ctx.fillStyle = player.color || "#f1c40f";
    ctx.beginPath();
    ctx.arc(myX, myY, Math.max(4, px * 0.5), 0, Math.PI * 2);
    ctx.fill();
    ctx.strokeStyle = "white";
    ctx.lineWidth = 2;
    ctx.stroke();

    // Legende bauen
    const legend = document.getElementById("worldMapLegend");
    legend.innerHTML = "<strong>Legende (klicken = Navi-Ziel setzen)</strong><br>";
    poiGroups.forEach(group => {
        if (group.list.length === 0) return;
        legend.innerHTML += `<span style="color:${group.color};">●</span> ${group.icon} ${group.label} (${group.list.length})<br>`;
    });

    // Klick auf die Karte -> nächstgelegenen Marker als Navi-Ziel setzen
    canvas.onclick = (e) => {
        const rect = canvas.getBoundingClientRect();
        const clickX = (e.clientX - rect.left) * (canvas.width / rect.width);
        const clickY = (e.clientY - rect.top) * (canvas.height / rect.height);

        let closest = null;
        let closestDist = Infinity;
        markers.forEach(m => {
            const d = Math.hypot(m.x - clickX, m.y - clickY);
            if (d < closestDist) { closestDist = d; closest = m; }
        });

        if (closest && closestDist < 20) {
            naviTarget = { x: closest.worldX, y: closest.worldY, world: player.world, label: closest.label };
            updateNaviDisplay();
            addChatMessage(`🧭 Navigation gestartet: ${closest.label}`, "system");
            closeWorldMap();
        }
    };
}

function closeWorldMap() {
    document.getElementById("worldMapBox").style.display = "none";
}

function updateNaviDisplay() {
    const el = document.getElementById("naviDisplay");
    if (!el) return;
    if (!naviTarget || naviTarget.world !== player.world) {
        el.style.display = "none";
        return;
    }

    const dx = naviTarget.x - player.x;
    const dy = naviTarget.y - player.y;
    const dist = Math.hypot(dx, dy);

    if (dist < 50) {
        addChatMessage(`🧭 Ziel erreicht: ${naviTarget.label}`, "system");
        naviTarget = null;
        el.style.display = "none";
        return;
    }

    const angle = Math.atan2(dy, dx);
    el.style.display = "flex";
    document.getElementById("naviArrow").style.transform = `rotate(${angle}rad)`;
    document.getElementById("naviLabel").textContent = `${naviTarget.label} - ${Math.round(dist)}m`;
}

function closeAdminMenu() {
    document.getElementById("adminBox").style.display = "none";
}

// -------------------------------------------------------------
// TAXI (NEU)
// -------------------------------------------------------------
// -------------------------------------------------------------
// BEFEHLS-FENSTER: alle Befehle als Buttons + Eingabefeld (NEU)
// -------------------------------------------------------------
const COMMAND_CATEGORIES = [
    {
        cat: "Allgemein",
        items: [
            { label: "Weltkarte öffnen", cmd: "map" },
            { label: "Debug-Overlay an/aus", cmd: "debug" },
            { label: "Navigation abbrechen", cmd: "navi cancel" }
        ]
    },
    {
        cat: "Autos & Aktionen",
        items: [
            { label: "Autoschlüssel geben", cmd: "key {0}", args: ["Name"] },
            { label: "Autoschlüssel entziehen", cmd: "key remove {0}", args: ["Name"] },
            { label: "Hausschlüssel geben", cmd: "housekey {0}", args: ["Name"] },
            { label: "Hausschlüssel entziehen", cmd: "housekey remove {0}", args: ["Name"] },
            { label: "Raubüberfall", cmd: "rob" },
            { label: "Auto kurzschließen", cmd: "hotwire" },
            { label: "Verhaften (Polizei)", cmd: "arrest {0}", args: ["Name"] },
            { label: "Sicherheitszone-Status anzeigen", cmd: "pvp" },
            { label: "Angreifen", cmd: "attack" }
        ]
    },
    {
        cat: "Bande",
        items: [
            { label: "Bande gründen (5000$)", cmd: "gang create {0} {1}", args: ["Name", "Tag"] },
            { label: "Einladen", cmd: "gang invite {0}", args: ["Name"] },
            { label: "Beitreten", cmd: "gang accept {0}", args: ["Bandenname"] },
            { label: "Kicken", cmd: "gang kick {0}", args: ["Name"] },
            { label: "Verlassen", cmd: "gang leave" },
            { label: "Einzahlen", cmd: "gang deposit {0}", args: ["Betrag"] },
            { label: "Auszahlen", cmd: "gang withdraw {0}", args: ["Betrag"] },
            { label: "Info", cmd: "gang info" }
        ]
    },
    {
        cat: "Jobs",
        items: [
            { label: "In geschützten Job einladen", cmd: "job invite {0}", args: ["Name"] }
        ]
    },
    {
        cat: "Abschleppen",
        items: [
            { label: "Fahrzeug anhängen", cmd: "tow hook" },
            { label: "Abliefern (+50$)", cmd: "tow dropoff" },
            { label: "Loslassen", cmd: "tow release" }
        ]
    },
    {
        cat: "Shop & Tankstelle",
        items: [
            { label: "Shop kaufen", cmd: "shop buy" },
            { label: "Shop freigeben", cmd: "shop unclaim" },
            { label: "Shop-Besitzer anzeigen", cmd: "shop info" },
            { label: "Tankstelle kaufen", cmd: "station buy" },
            { label: "Tankstelle freigeben", cmd: "station unclaim" },
            { label: "Tankstellen-Besitzer anzeigen", cmd: "station info" }
        ]
    },
    {
        cat: "Versicherung",
        items: [
            { label: "Auto versichern", cmd: "car insure" },
            { label: "Versicherung kündigen", cmd: "car uninsure" }
        ]
    },
    {
        cat: "Tuning",
        items: [
            { label: "Tuning-Menü öffnen (an der Werkstatt)", cmd: "tune" }
        ]
    },
    {
        cat: "Anhänger",
        items: [
            { label: "Anhänger kaufen", cmd: "trailer buy" },
            { label: "Anhänger ankuppeln", cmd: "trailer hitch" },
            { label: "Anhänger abkuppeln", cmd: "trailer unhitch" },
            { label: "Auto auf Anhänger laden", cmd: "trailer load" },
            { label: "Auto vom Anhänger holen", cmd: "trailer unload" }
        ]
    },
    {
        cat: "Nummernschild",
        items: [
            { label: "Nummernschild setzen", cmd: "plate {0}", args: ["Text"] }
        ]
    },
    {
        cat: "Immobilien-Vermietung",
        items: [
            { label: "Haus zur Miete anbieten", cmd: "house setrent {0}", args: ["Betrag"] },
            { label: "Mietangebot zurückziehen", cmd: "house unrent" },
            { label: "Haus mieten", cmd: "house rent" },
            { label: "Mieter kündigen", cmd: "house evict" },
            { label: "Selbst ausziehen", cmd: "house moveout" }
        ]
    },
    {
        cat: "Kopfgeld",
        items: [
            { label: "Kopfgeld aussetzen", cmd: "bounty {0} {1}", args: ["Name", "Betrag"] },
            { label: "Kopfgeld anzeigen", cmd: "bounty info {0}", args: ["Name"] }
        ]
    },
    {
        cat: "Handschellen (Polizei)",
        items: [
            { label: "Handschellen anlegen", cmd: "cuff {0}", args: ["Name"] },
            { label: "Handschellen abnehmen", cmd: "uncuff {0}", args: ["Name"] },
            { label: "Ins Auto setzen", cmd: "putin {0}", args: ["Name"] },
            { label: "Aus Auto holen", cmd: "takeout {0}", args: ["Name"] },
            { label: "Am Gefängnis abliefern", cmd: "jaildropoff {0}", args: ["Name"] }
        ]
    },
    {
        cat: "Job-Funk",
        items: [
            { label: "Funkspruch senden", cmd: "funk {0}", args: ["Nachricht"] }
        ]
    },
    {
        cat: "Handy",
        items: [
            { label: "Handy öffnen", cmd: "phone" },
            { label: "Kontakt anfragen", cmd: "phone add {0}", args: ["Name"] },
            { label: "Anfrage annehmen", cmd: "phone accept {0}", args: ["Name"] },
            { label: "Kontakt entfernen", cmd: "phone remove {0}", args: ["Name"] },
            { label: "SMS senden", cmd: "sms {0} {1}", args: ["Name", "Nachricht"] },
            { label: "Anrufen", cmd: "call {0}", args: ["Name"] }
        ]
    },
    {
        cat: "Einsatz-Tablet",
        items: [
            { label: "Tablet öffnen", cmd: "tablet" }
        ]
    },
    {
        cat: "Support",
        items: [
            { label: "Tickets öffnen", cmd: "ticket" }
        ]
    },
    {
        cat: "Controller",
        items: [
            { label: "Controller-Einstellungen öffnen", cmd: "controller" }
        ]
    },
    {
        cat: "Börse",
        items: [
            { label: "Börse öffnen", cmd: "börse" }
        ]
    },
    {
        cat: "Schwarzmarkt",
        items: [
            { label: "Gestohlenes Auto beim Hehler verkaufen", cmd: "verkaufen" }
        ]
    },
    {
        cat: "Leitstelle",
        items: [
            { label: "Leitstelle öffnen", cmd: "leitstelle" }
        ]
    },
    {
        cat: "Feuerwehr",
        items: [
            { label: "Nächstes Feuer löschen", cmd: "fire extinguish" }
        ]
    },
    {
        cat: "Tore",
        items: [
            { label: "Tor beanspruchen", cmd: "gate claim" },
            { label: "Tor freigeben", cmd: "gate unclaim" },
            { label: "Tor-Info anzeigen", cmd: "gate info" },
            { label: "Schlüssel vergeben", cmd: "gatekey {0}", args: ["Name"] },
            { label: "Schlüssel entziehen", cmd: "gatekey remove {0}", args: ["Name"] }
        ]
    }
];

const COMMAND_CATEGORIES_ADMIN = [
    {
        cat: "Admin",
        items: [
            { label: "Admin-Menü öffnen", cmd: "admin" },
            { label: "Neustart ankündigen", cmd: "restart {0}", args: ["Sekunden (leer=60)"] },
            { label: "Neustart abbrechen", cmd: "restart cancel" },
            { label: "Dienstmodus an/aus", cmd: "admin duty" },
            { label: "Tor auf Job beschränken", cmd: "gate setjob {0}", args: ["Jobname ('none' zum Entfernen)"] }
        ]
    }
];

function openCommandsWindow() {
    const box = document.getElementById("commandsBox");
    const list = document.getElementById("cmdList");
    list.innerHTML = "";

    const categories = player.isAdmin
        ? [...COMMAND_CATEGORIES, ...COMMAND_CATEGORIES_ADMIN]
        : COMMAND_CATEGORIES;

    categories.forEach((group, gi) => {
        const catEl = document.createElement("div");
        catEl.className = "cmd-cat";
        catEl.textContent = group.cat;
        list.appendChild(catEl);

        group.items.forEach((item, ii) => {
            const row = document.createElement("div");
            row.className = "cmd-row";

            let inputsHtml = "";
            (item.args || []).forEach((placeholder, ai) => {
                inputsHtml += `<input type="text" id="cmdArg_${gi}_${ii}_${ai}" placeholder="${escapeHtmlAdmin(placeholder)}">`;
            });

            row.innerHTML = `
                <span class="cmd-label">${escapeHtmlAdmin(item.label)}</span>
                ${inputsHtml}
                <button data-gi="${gi}" data-ii="${ii}">Ausführen</button>
            `;
            list.appendChild(row);
        });
    });

    list.querySelectorAll("button[data-gi]").forEach(btn => {
        btn.onclick = () => {
            const gi = Number(btn.dataset.gi);
            const ii = Number(btn.dataset.ii);
            const categories2 = player.isAdmin ? [...COMMAND_CATEGORIES, ...COMMAND_CATEGORIES_ADMIN] : COMMAND_CATEGORIES;
            const item = categories2[gi].items[ii];

            let finalCmd = item.cmd;
            (item.args || []).forEach((placeholder, ai) => {
                const input = document.getElementById(`cmdArg_${gi}_${ii}_${ai}`);
                const val = input ? input.value.trim() : "";
                finalCmd = finalCmd.replace(`{${ai}}`, val);
            });
            finalCmd = finalCmd.trim().replace(/\s+/g, " ");

            handleChatCommand("/" + finalCmd);
        };
    });

    box.style.display = "flex";
}

function closeCommandsWindow() {
    document.getElementById("commandsBox").style.display = "none";
}

function runFreeCommand() {
    const input = document.getElementById("cmdFreeInput");
    let text = input.value.trim();
    if (!text) return;
    if (!text.startsWith("/")) text = "/" + text;

    handleChatCommand(text);
    input.value = "";
}

function openJobMenu() {
    if (!ws) return;
    ws.send(JSON.stringify({ type: "job_menu_open" }));
    document.getElementById("jobMenuBox").style.display = "flex";
}

function closeJobMenu() {
    document.getElementById("jobMenuBox").style.display = "none";
}

// -------------------------------------------------------------
// FAHRZEUG-TUNING (NEU)
// -------------------------------------------------------------
const TUNE_STAT_LABELS = { speed: "🏎️ Motor (Höchstgeschw.)", accel: "⚡ Beschleunigung", brake: "🛑 Bremsen" };

function openTuneWindow(msg) {
    const box = document.getElementById("tuneBox");
    const body = document.getElementById("tuneBody");
    body.innerHTML = "";

    ["speed", "accel", "brake"].forEach(stat => {
        const level = msg.levels[stat];
        const cost = msg.costs[stat];
        const row = document.createElement("div");
        row.className = "job-menu-row";
        row.innerHTML = `
            <span>${TUNE_STAT_LABELS[stat]} - Stufe ${level}/${msg.maxLevel}</span>
            <button class="jmBtn" ${cost === null ? "disabled" : ""} data-stat="${stat}">
                ${cost === null ? "Maximal" : `Aufwerten (${cost}$)`}
            </button>
        `;
        body.appendChild(row);
    });

    const paintRow = document.createElement("div");
    paintRow.className = "job-menu-row";
    paintRow.innerHTML = `
        <span>🎨 Lackierung (${msg.paintCost}$)</span>
        <input type="color" id="tunePaintColor" value="${msg.currentColor || "#c0392b"}" style="width:50px; height:30px; padding:0; border:none; cursor:pointer;">
        <button class="jmBtn" id="tunePaintBtn">Lackieren</button>
    `;
    body.appendChild(paintRow);

    body.querySelectorAll(".jmBtn[data-stat]").forEach(btn => {
        btn.onclick = () => {
            ws.send(JSON.stringify({ type: "tune_upgrade", stat: btn.dataset.stat }));
            closeTuneWindow();
        };
    });
    document.getElementById("tunePaintBtn").onclick = () => {
        const color = document.getElementById("tunePaintColor").value;
        ws.send(JSON.stringify({ type: "tune_paint", color }));
        closeTuneWindow();
    };

    box.style.display = "flex";
}

function closeTuneWindow() {
    document.getElementById("tuneBox").style.display = "none";
}

function renderJobMenu(msg) {
    const body = document.getElementById("jobMenuBody");
    const title = document.getElementById("jobMenuTitle");
    body.innerHTML = "";

    if (!msg.jobType) {
        title.textContent = "📋 Job-Menü";
        body.innerHTML = `<p style="color:#999;">Du hast aktuell keinen Job mit Menü-Aktionen. (Taxi, Polizei, Sanitäter, Abschlepper haben eins)</p>`;
        return;
    }

    title.textContent = `📋 Job-Menü - ${msg.jobName || msg.jobType}`;

    if (msg.jobType === "taxi") {
        if (!msg.requests || msg.requests.length === 0) {
            body.innerHTML = `<p style="color:#999;">Aktuell wartet niemand auf ein Taxi.</p>`;
            return;
        }
        msg.requests.forEach(r => {
            const row = document.createElement("div");
            row.className = "job-menu-row";
            row.innerHTML = `
                <span>${escapeHtmlAdmin(r.username)} (${escapeHtmlAdmin(r.world)})</span>
                <button class="jmBtn" data-action="taxi_pickup" data-id="${r.playerId}">Abholen</button>
            `;
            body.appendChild(row);
        });
    }

    if (msg.jobType === "police") {
        body.innerHTML = "";

        const wantedTitle = document.createElement("h4");
        wantedTitle.textContent = "Gesucht";
        wantedTitle.style.cssText = "margin:6px 0 4px; color:#e06c6c; font-size:13px;";
        body.appendChild(wantedTitle);

        if (!msg.wanted || msg.wanted.length === 0) {
            body.innerHTML += `<p style="color:#999; font-size:12px;">Aktuell ist niemand gesucht.</p>`;
        } else {
            msg.wanted.forEach(w => {
                const row = document.createElement("div");
                row.className = "job-menu-row";
                row.innerHTML = `
                    <span>${escapeHtmlAdmin(w.username)} ${"⭐".repeat(w.wantedLevel)} (${escapeHtmlAdmin(w.world)})</span>
                    <span>
                        <button class="jmBtn" data-action="arrest" data-username="${escapeHtmlAdmin(w.username)}">Sofort verhaften</button>
                        <button class="jmBtn" data-action="cuff" data-username="${escapeHtmlAdmin(w.username)}">Fesseln</button>
                    </span>
                `;
                body.appendChild(row);
            });
        }

        const cuffedTitle = document.createElement("h4");
        cuffedTitle.textContent = "Gefesselte Personen";
        cuffedTitle.style.cssText = "margin:14px 0 4px; color:#f5d90a; font-size:13px;";
        body.appendChild(cuffedTitle);

        if (!msg.cuffed || msg.cuffed.length === 0) {
            body.innerHTML += `<p style="color:#999; font-size:12px;">Aktuell niemand gefesselt.</p>`;
        } else {
            msg.cuffed.forEach(c => {
                const row = document.createElement("div");
                row.className = "job-menu-row";
                let buttons = `<button class="jmBtn" data-action="uncuff" data-username="${escapeHtmlAdmin(c.username)}">Losbinden</button>`;

                if (c.inMyCar) {
                    buttons += `<button class="jmBtn" data-action="takeout" data-username="${escapeHtmlAdmin(c.username)}">Aus Auto holen</button>`;
                    buttons += `<button class="jmBtn" data-action="jaildropoff" data-username="${escapeHtmlAdmin(c.username)}">Am Gefängnis abliefern</button>`;
                } else if (!c.inAnyCar && msg.isDriving) {
                    buttons += `<button class="jmBtn" data-action="putin" data-username="${escapeHtmlAdmin(c.username)}">Ins Auto setzen</button>`;
                }

                row.innerHTML = `
                    <span>${escapeHtmlAdmin(c.username)} 🔗 (${escapeHtmlAdmin(c.world)})${c.inMyCar ? " - bei dir im Auto" : c.inAnyCar ? " - in einem Auto" : ""}</span>
                    <span>${buttons}</span>
                `;
                body.appendChild(row);
            });
        }
    }

    if (msg.jobType === "medic") {
        if (!msg.injured || msg.injured.length === 0) {
            body.innerHTML = `<p style="color:#999;">Aktuell ist niemand verletzt.</p>`;
            return;
        }
        msg.injured.forEach(i => {
            const row = document.createElement("div");
            row.className = "job-menu-row";
            row.innerHTML = `
                <span>${escapeHtmlAdmin(i.username)} - ${i.health}% Leben (${escapeHtmlAdmin(i.world)})</span>
                <button class="jmBtn" data-action="heal" data-id="${i.playerId}">Heilen</button>
            `;
            body.appendChild(row);
        });
    }

    if (msg.jobType === "mechanic") {
        if (!msg.damaged || msg.damaged.length === 0) {
            body.innerHTML = `<p style="color:#999;">Kein beschädigtes Fahrzeug in der Nähe.</p>`;
            return;
        }
        msg.damaged.forEach(d => {
            const row = document.createElement("div");
            row.className = "job-menu-row";
            row.innerHTML = `
                <span>${escapeHtmlAdmin(d.model)} (${escapeHtmlAdmin(d.ownerName)}) - ${d.health}% Zustand</span>
                <button class="jmBtn" data-action="mechanic_repair" data-id="${d.carId}">Reparieren</button>
            `;
            body.appendChild(row);
        });
    }

    if (msg.jobType === "tow") {
        if (!msg.isDriving) {
            body.innerHTML = `<p style="color:#999;">Du musst im Abschleppwagen sitzen, um diese Aktionen zu nutzen.</p>`;
            return;
        }
        if (msg.isTowing) {
            body.innerHTML = `
                <div class="job-menu-row"><span>Fahrzeug angehängt</span></div>
                <button class="jmBtn wide" data-action="tow_dropoff">Am Abschlepphof abliefern (+50$)</button>
                <button class="jmBtn wide secondary" data-action="tow_release">Loslassen</button>
            `;
        } else {
            body.innerHTML = `
                <div class="job-menu-row"><span>Kein Fahrzeug angehängt</span></div>
                <button class="jmBtn wide" data-action="tow_hook">Freistehendes Fahrzeug anhängen</button>
            `;
        }
    }

    if (msg.jobType === "fire") {
        if (!msg.fires || msg.fires.length === 0) {
            body.innerHTML = `<p style="color:#999;">Aktuell brennt nichts.</p>`;
            return;
        }
        msg.fires.forEach(f => {
            const row = document.createElement("div");
            row.className = "job-menu-row";
            row.innerHTML = `
                <span>🔥 Feuer bei (${f.x}, ${f.y}) - ${f.intensity}%</span>
                <button class="jmBtn" data-action="extinguish" data-id="${f.id}">Löschen</button>
            `;
            body.appendChild(row);
        });
    }

    body.querySelectorAll(".jmBtn").forEach(btn => {
        btn.onclick = () => {
            const action = btn.dataset.action;
            if (action === "taxi_pickup") {
                ws.send(JSON.stringify({ type: "taxi_pickup", playerId: Number(btn.dataset.id) }));
            } else if (action === "arrest") {
                ws.send(JSON.stringify({ type: "arrest_player", username: btn.dataset.username }));
            } else if (action === "heal") {
                ws.send(JSON.stringify({ type: "medic_heal", targetId: Number(btn.dataset.id) }));
            } else if (action === "mechanic_repair") {
                ws.send(JSON.stringify({ type: "mechanic_repair", carId: Number(btn.dataset.id) }));
            } else if (action === "cuff") {
                ws.send(JSON.stringify({ type: "police_cuff", username: btn.dataset.username }));
            } else if (action === "uncuff") {
                ws.send(JSON.stringify({ type: "police_uncuff", username: btn.dataset.username }));
            } else if (action === "putin") {
                ws.send(JSON.stringify({ type: "police_put_in_car", username: btn.dataset.username }));
            } else if (action === "takeout") {
                ws.send(JSON.stringify({ type: "police_take_out_of_car", username: btn.dataset.username }));
            } else if (action === "jaildropoff") {
                ws.send(JSON.stringify({ type: "police_jail_dropoff", username: btn.dataset.username }));
            } else if (action === "tow_hook") {
                ws.send(JSON.stringify({ type: "tow_hook" }));
            } else if (action === "tow_dropoff") {
                ws.send(JSON.stringify({ type: "tow_dropoff" }));
            } else if (action === "tow_release") {
                ws.send(JSON.stringify({ type: "tow_release" }));
            } else if (action === "extinguish") {
                ws.send(JSON.stringify({ type: "fire_extinguish", fireId: Number(btn.dataset.id) }));
            }
            closeJobMenu();
        };
    });
}

function openTaxiWindow(requests) {
    const box = document.getElementById("taxiBox");
    const list = document.getElementById("taxiRequestList");
    list.innerHTML = "";

    if (!requests || requests.length === 0) {
        list.innerHTML = `<p style="color:#888;">Aktuell wartet niemand auf ein Taxi.</p>`;
    } else {
        requests.forEach(r => {
            const div = document.createElement("div");
            div.style.display = "flex";
            div.style.justifyContent = "space-between";
            div.style.alignItems = "center";
            div.style.padding = "6px 0";
            div.innerHTML = `
                <span>${escapeHtmlAdmin(r.username)} (${escapeHtmlAdmin(r.world)})</span>
                <button class="taxiPickupBtn" data-id="${r.playerId}">Abholen</button>
            `;
            list.appendChild(div);
        });
    }

    if (!list.dataset.bound) {
        list.addEventListener("click", e => {
            const btn = e.target.closest(".taxiPickupBtn");
            if (btn) {
                ws.send(JSON.stringify({ type: "taxi_pickup", playerId: Number(btn.dataset.id) }));
                closeTaxiWindow();
            }
        });
        list.dataset.bound = "true";
    }

    box.style.display = "block";
}

function closeTaxiWindow() {
    document.getElementById("taxiBox").style.display = "none";
}

function refreshAdminMenu() {
    ws.send(JSON.stringify({ type: "admin_get_players" }));
}

function toggleAdminDuty() {
    ws.send(JSON.stringify({ type: "admin_toggle_duty" }));
}

function triggerServerRestart() {
    const secondsStr = prompt("Neustart in wie vielen Sekunden ankündigen?", "60");
    if (secondsStr === null) return;
    const seconds = Number(secondsStr) || 60;

    if (!confirm(`Server-Neustart in ${seconds}s ankündigen? Alle Spieler werden anschließend zur Startseite geschickt.`)) {
        return;
    }
    ws.send(JSON.stringify({ type: "admin_restart_server", seconds }));
}

function cancelServerRestart() {
    ws.send(JSON.stringify({ type: "admin_restart_server", cancel: true }));
}

function renderAdminPlayerList(players) {
    const list = document.getElementById("adminPlayerList");
    list.innerHTML = "";

    players.forEach(pl => {
        const div = document.createElement("div");
        div.className = "admin-player-row";
        div.innerHTML = `
            <div class="admin-player-head">
                <strong>${escapeHtmlAdmin(pl.username)}</strong>
                <span class="admin-player-meta">#${pl.id} — ${escapeHtmlAdmin(pl.world)} (${pl.x}, ${pl.y})</span>
            </div>
            <div class="admin-player-actions">
                <input type="number" class="moneyInput" data-id="${pl.id}" value="${pl.money}" style="width:90px;">
                <button class="setMoneyBtn" data-id="${pl.id}">Setzen</button>
                <button class="teleportBtn" data-id="${pl.id}">Teleport zu</button>
                <button class="lockBtn danger" data-id="${pl.id}">Sperren</button>
            </div>
        `;
        list.appendChild(div);
    });

    if (!list.dataset.bound) {
        list.addEventListener("click", e => {
            const setBtn = e.target.closest(".setMoneyBtn");
            const tpBtn = e.target.closest(".teleportBtn");
            const lockBtn = e.target.closest(".lockBtn");

            if (setBtn) {
                const id = setBtn.dataset.id;
                const input = list.querySelector(`.moneyInput[data-id="${id}"]`);
                const amount = Number(input.value);
                if (!isNaN(amount)) {
                    ws.send(JSON.stringify({ type: "admin_set_money", targetId: Number(id), amount }));
                }
            }
            if (tpBtn) {
                ws.send(JSON.stringify({ type: "admin_teleport_to", targetId: Number(tpBtn.dataset.id) }));
                closeAdminMenu();
            }
            if (lockBtn) {
                if (confirm("Diesen Spieler wirklich sperren? Er wird sofort getrennt.")) {
                    ws.send(JSON.stringify({ type: "admin_lock_player", targetId: Number(lockBtn.dataset.id) }));
                    setTimeout(refreshAdminMenu, 300);
                }
            }
        });
        list.dataset.bound = "true";
    }
}

function escapeHtmlAdmin(str) {
    const div = document.createElement("div");
    div.textContent = str;
    return div.innerHTML;
}

function updateOnlineCount(count) {
    const el = document.getElementById("onlineCount");
    if (el) el.textContent = `👥 ${count} online`;
}

function updateWantedDisplay() {
    const el = document.getElementById("wantedDisplay");
    if (!el) return;

    if (!player.wantedLevel) {
        el.textContent = "";
        return;
    }
    el.textContent = "⭐".repeat(player.wantedLevel);
}

function updatePvpDisplay() {
    const el = document.getElementById("pvpDisplay");
    if (!el) return;
    el.textContent = player.inSafeZone ? "🛡️ Sicherheitszone" : "⚔️ PvP aktiv";
    el.style.color = player.inSafeZone ? "#6fbf73" : "#e06c6c";
}

function updateLevelDisplay() {
    const el = document.getElementById("levelDisplay");
    if (!el) return;
    const xpInLevel = (player.xp || 0) % 500;
    el.textContent = `⭐ Lvl ${player.level || 1} (${xpInLevel}/500 XP)`;
}

function formatEventTimeLeft(endsAt) {
    const diff = endsAt - Date.now();
    if (diff <= 0) return "";
    const h = Math.floor(diff / 3600000);
    const m = Math.floor((diff % 3600000) / 60000);
    return h > 0 ? `${h}h ${m}min` : `${m}min`;
}

function updateEventDisplay() {
    const el = document.getElementById("eventBanner");
    if (!el) return;

    const parts = [];
    if (activeEvents.doubleXp && activeEvents.doubleXp.active) {
        parts.push(`⭐ Doppel-XP (${formatEventTimeLeft(activeEvents.doubleXp.endsAt)})`);
    }
    if (activeEvents.discount && activeEvents.discount.active) {
        parts.push(`💰 ${activeEvents.discount.percent}% Rabatt (${formatEventTimeLeft(activeEvents.discount.endsAt)})`);
    }

    if (parts.length === 0) {
        el.style.display = "none";
    } else {
        el.style.display = "block";
        el.textContent = parts.join("   •   ");
    }
}

// Restzeit-Anzeige jede Minute nachschärfen, auch ohne neue Nachricht vom Server
setInterval(updateEventDisplay, 30000);

function updateDutyDisplay() {
    const el = document.getElementById("dutyDisplay");
    if (!el) return;
    el.textContent = player.adminDuty ? "🛡️ Im Dienst" : "";
}

function updateJailDisplay() {
    const box = document.getElementById("jailBox");
    if (!box) return;

    if (!jailedUntil || jailedUntil <= Date.now()) {
        box.style.display = "none";
        return;
    }

    const remain = Math.max(0, Math.ceil((jailedUntil - Date.now()) / 1000));
    document.getElementById("jailCountdown").textContent = remain + "s";
    box.style.display = "flex";
}

setInterval(updateJailDisplay, 1000);

let deathScreenTimeout = null;
function showDeathScreen(killerName) {
    const box = document.getElementById("deathScreen");
    const text = document.getElementById("deathScreenText");
    text.textContent = killerName ? `Du wurdest von ${killerName} getötet.` : "Du bist gestorben.";
    box.style.display = "flex";

    if (deathScreenTimeout) clearTimeout(deathScreenTimeout);
    deathScreenTimeout = setTimeout(() => {
        box.style.display = "none";
    }, 3200);
}

// -------------------------------------------------------------
// UHR (NEU)
// -------------------------------------------------------------
const DAY_LENGTH_MINUTES = 24; // muss zum Server passen

function updateClockDisplay() {
    const el = document.getElementById("clockDisplay");
    if (!el) return;

    const h = Math.floor(gameHour) % 24;
    const m = Math.floor((gameHour % 1) * 60);
    const icon = (gameHour >= 6 && gameHour < 19) ? "🌞" : "🌙";
    const weatherIcon = weather === "rain" ? " 🌧️" : "";

    el.textContent = `${icon} ${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}${weatherIcon}`;
}

// Uhrzeit zwischen den Server-Syncs (alle 5s) sanft weiterlaufen lassen
setInterval(() => {
    gameHour = (gameHour + 24 / (DAY_LENGTH_MINUTES * 60)) % 24;
    updateClockDisplay();
}, 1000);

async function loadVersionDisplay() {
    try {
        const res = await fetch("/api/status");
        const data = await res.json();
        const el = document.getElementById("versionDisplay");
        if (el && data.version) el.textContent = "v" + data.version;
    } catch {}
}
loadVersionDisplay();

function useItem(itemId) {
    ws.send(JSON.stringify({
        type: "use_item",
        itemId
    }));
}

function buyItem(itemId) {
    ws.send(JSON.stringify({
        type: "shop_buy",
        itemId
    }));
}

function setShopItemPrice(itemId) {
    const input = document.getElementById(`shopPriceInput_${itemId}`);
    const price = Number(input.value);
    if (isNaN(price) || price < 1) {
        addChatMessage("Ungültiger Preis.", "system");
        return;
    }
    ws.send(JSON.stringify({
        type: "shop_set_price",
        itemId,
        price
    }));
}

// -------------------------------------------------------------
// CAMERA (mit Zentrierung bei kleinen Maps, folgt Auto beim Fahren)
// -------------------------------------------------------------
function getCamera() {
    const map = maps[player.world];
    if (!map || !map.tiles || map.tiles.length === 0) {
        return { x: 0, y: 0 };
    }

    let focusX = player.x;
    let focusY = player.y;

    if (drivingCarId) {
        const c = cars.find(c => c.id === drivingCarId);
        if (c) {
            focusX = c.x;
            focusY = c.y;
        }
    }

    const mapWidth = map.tiles[0].length * 32;
    const mapHeight = map.tiles.length * 32;

    let camX = focusX - canvas.width / 2;
    let camY = focusY - canvas.height / 2;

    if (mapWidth < canvas.width) {
        camX = -(canvas.width / 2 - mapWidth / 2);
    } else {
        if (camX < 0) camX = 0;
        if (camX > mapWidth - canvas.width)
            camX = mapWidth - canvas.width;
    }

    if (mapHeight < canvas.height) {
        camY = -(canvas.height / 2 - mapHeight / 2);
    } else {
        if (camY < 0) camY = 0;
        if (camY > mapHeight - canvas.height)
            camY = mapHeight - canvas.height;
    }

    return { x: Math.round(camX), y: Math.round(camY) };
}

// -------------------------------------------------------------
// MOVEMENT + Collision
// -------------------------------------------------------------
function isAnyOverlayOpen() {
    const ids = [
        "commandsBox", "phoneBox", "tabletBox", "clothingBox", "wardrobeBox",
        "shopWindow", "garageBox", "jobBox", "trunkBox", "houseBox",
        "adminBox", "taxiBox", "worldMapBox", "jobMenuBox", "tuneBox", "trailerShopBox", "ticketBox", "gamepadSettingsBox", "stockBox", "leitstelleBox"
    ];
    return ids.some(id => {
        const el = document.getElementById(id);
        return el && el.style.display !== "none" && el.style.display !== "";
    });
}

document.addEventListener("keydown", e => {
    keys[e.key] = true;

    if (!ws) return;
    if (isAnyOverlayOpen()) return; // Fenster offen (z.B. /commands) - alle Tastenkürzel gesperrt

    if (e.key.toLowerCase() === "e") {
        if (drivingCarId) {
            // Direkt einparken, falls vor einer eigenen Garage - sonst normal aussteigen/tanken/reparieren
            ws.send(JSON.stringify({ type: "car_exit_or_park" }));
        } else if (passengerCarId) {
            // Als Mitfahrer aussteigen
            ws.send(JSON.stringify({ type: "car_exit_passenger" }));
        } else {
            // Erst versuchen einzusteigen (Fahrer oder Mitfahrer), sonst normale Interaktion
            ws.send(JSON.stringify({ type: "car_enter" }));
            ws.send(JSON.stringify({
                type: "interact",
                x: player.x,
                y: player.y,
                world: player.world
            }));
        }
    }

    if (e.key.toLowerCase() === "j") {
        openJobMenu();
    }

    if (e.key.toLowerCase() === "v") {
        toggleMic();
    }

    if (e.key.toLowerCase() === "p") {
        openPhone();
    }

    if (e.key.toLowerCase() === "o") {
        openTablet();
    }

    if (e.key.toLowerCase() === "k") {
        ws.send(JSON.stringify({ type: "trailer_toggle_hitch" }));
    }

    // Blinker/Licht/Warnblinker nur während der Fahrt
    if (drivingCarId) {
        if (e.key === "1") ws.send(JSON.stringify({ type: "car_toggle", what: "left" }));
        if (e.key === "2") ws.send(JSON.stringify({ type: "car_toggle", what: "right" }));
        if (e.key === "3") ws.send(JSON.stringify({ type: "car_toggle", what: "headlights" }));
        if (e.key === "4") ws.send(JSON.stringify({ type: "car_toggle", what: "hazard" }));
        if (e.key === "5") ws.send(JSON.stringify({ type: "car_toggle", what: "emergency" }));
        if (e.key.toLowerCase() === "r") cycleRadio();
    }

    // Q-Taste: Tor öffnen/schließen, falls eins in der Nähe ist - geht zu Fuß und im Auto
    if (e.key.toLowerCase() === "q") {
        ws.send(JSON.stringify({ type: "gate_toggle_nearby" }));
    }

    // Tanken/Reparieren nur während der Fahrt
    if (drivingCarId && e.key.toLowerCase() === "q") {
        ws.send(JSON.stringify({ type: "car_use_station" }));
    }

    // Kofferraum öffnen (im Auto oder neben dem eigenen geparkten Auto)
    if (e.key.toLowerCase() === "t") {
        ws.send(JSON.stringify({ type: "trunk_open" }));
    }
});
// Shop/Garage/Jobcenter/Kofferraum schließen (ESC)
document.addEventListener("keydown", e => {
    if (e.key === "Escape") {
        shopWindow.style.display = "none";
        const garageBox = document.getElementById("garageBox");
        if (garageBox) garageBox.style.display = "none";
        const jobBox = document.getElementById("jobBox");
        if (jobBox) jobBox.style.display = "none";
        const trunkBox = document.getElementById("trunkBox");
        if (trunkBox) trunkBox.style.display = "none";
        const houseBox = document.getElementById("houseBox");
        if (houseBox) houseBox.style.display = "none";
        const adminBox = document.getElementById("adminBox");
        if (adminBox) adminBox.style.display = "none";
        const taxiBox = document.getElementById("taxiBox");
        if (taxiBox) taxiBox.style.display = "none";
        const worldMapBox = document.getElementById("worldMapBox");
        if (worldMapBox) worldMapBox.style.display = "none";
        const jobMenuBox = document.getElementById("jobMenuBox");
        if (jobMenuBox) jobMenuBox.style.display = "none";
        const commandsBox = document.getElementById("commandsBox");
        if (commandsBox) commandsBox.style.display = "none";
        const tuneBox = document.getElementById("tuneBox");
        if (tuneBox) tuneBox.style.display = "none";
        const phoneBox = document.getElementById("phoneBox");
        if (phoneBox) phoneBox.style.display = "none";
        const clothingBox = document.getElementById("clothingBox");
        if (clothingBox) clothingBox.style.display = "none";
        const trailerShopBox = document.getElementById("trailerShopBox");
        if (trailerShopBox) trailerShopBox.style.display = "none";
        const wardrobeBox = document.getElementById("wardrobeBox");
        if (wardrobeBox) wardrobeBox.style.display = "none";
        const tabletBox = document.getElementById("tabletBox");
        if (tabletBox) tabletBox.style.display = "none";
        const ticketBox = document.getElementById("ticketBox");
        if (ticketBox) ticketBox.style.display = "none";
        const gamepadSettingsBox = document.getElementById("gamepadSettingsBox");
        if (gamepadSettingsBox) gamepadSettingsBox.style.display = "none";
        const stockBox = document.getElementById("stockBox");
        if (stockBox) stockBox.style.display = "none";
        const leitstelleBox = document.getElementById("leitstelleBox");
        if (leitstelleBox && leitstelleBox.style.display !== "none") closeLeitstelle();
    }
});

document.addEventListener("keyup", e => {
    keys[e.key] = false;
});

function canMoveTo(nx, ny) {
    const map = maps[player.world];
    if (!map || !map.tiles) return true;

    const tileX = Math.floor(nx / 32);
    const tileY = Math.floor(ny / 32);

    // KARTENRAND: außerhalb der Map ist grundsätzlich gesperrt
    if (tileY < 0 || tileY >= map.tiles.length || tileX < 0 || tileX >= map.tiles[0].length) {
        return false;
    }

    // TILE COLLISION
    const tileId = map.tiles[tileY]?.[tileX];
    const tile = tileConfig[tileId];
    if (tile && tile.collision) return false;

    // OBJECT COLLISION
    if (map.objects) {
        for (const obj of map.objects) {
            const cfg = objectConfig[obj.type];
            if (!cfg || !cfg.collision) continue;
            if (obj.open) continue; // Tor/Tür ist offen -> keine Kollision

            const ox = obj.x;
            const oy = obj.y - (cfg.height - 32); // Objekt höher als Tile
            const ow = cfg.width;
            const oh = cfg.height;

            // Spieler-Hitbox (30x30)
            const px = nx;
            const py = ny;
            const pw = 30;
            const ph = 30;

            const hit =
                px < ox + ow &&
                px + pw > ox &&
                py < oy + oh &&
                py + ph > oy;

            if (hit) return false;
        }
    }

    return true;
}

function updateNeeds() {
    player.hunger -= 0.002;
    player.thirst -= 0.004;

    if (player.hunger < 0) player.hunger = 0;
    if (player.thirst < 0) player.thirst = 0;

    if (player.hunger === 0 || player.thirst === 0) {
        player.health -= 0.01;
        if (player.health < 0) player.health = 0;
    }
}

function updateStatusWindow() {
    const status = document.getElementById("statusWindow");

    status.innerHTML = `
        Leben: ${Math.round(player.health)}<br>
        Hunger: ${Math.round(player.hunger)}<br>
        Durst: ${Math.round(player.thirst)}<br>
        Geld: ${player.money}$
    `;
}

// -------------------------------------------------------------
// MOVEMENT (Fuß ODER Auto)
// -------------------------------------------------------------
function updateMovement() {
    // In Haft: keine Bewegung möglich
    if (jailedUntil && jailedUntil > Date.now()) return;

    // Mitfahrer: keine eigene Bewegung, Position kommt vom Server (ans Auto gekoppelt)
    if (passengerCarId) return;

    // Fahren: WASD steuert Gas/Bremse/Lenkung statt Laufen
    if (drivingCarId) {
        let throttle = 0;
        let steer = 0;

        if (keys["w"] || gamepadMove.w || touchMove.w) throttle = 1;
        if (keys["s"] || gamepadMove.s || touchMove.s) throttle = -1;
        if (keys["a"] || gamepadMove.a || touchMove.a) steer = -1;
        if (keys["d"] || gamepadMove.d || touchMove.d) steer = 1;

        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type: "car_control", throttle, steer }));
        }
        return;
    }

    let moved = false;

    let nx = player.x;
    let ny = player.y;

    if (keys["w"] || gamepadMove.w || touchMove.w) { ny -= 5; moved = true; }
    if (keys["s"] || gamepadMove.s || touchMove.s) { ny += 5; moved = true; }
    if (keys["a"] || gamepadMove.a || touchMove.a) { nx -= 5; moved = true; }
    if (keys["d"] || gamepadMove.d || touchMove.d) { nx += 5; moved = true; }

    if (canMoveTo(nx, ny)) {
        player.x = nx;
        player.y = ny;

        if (ws && moved && ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({
                type: "move",
                x: nx,
                y: ny
            }));
        }
    }
}

function renderInventoryWindow() {
    const list = document.getElementById("inventoryList");
    if (!list) return;

    list.innerHTML = "";

    (player.inventory || []).forEach(item => {
        const div = document.createElement("div");
        div.style.display = "flex";
        div.style.justifyContent = "space-between";
        div.style.padding = "5px";
        div.style.borderBottom = "1px solid #333";

        const clothingMatch = String(item.id).match(/^clothing_(\d+)$/);
        if (clothingMatch) {
            const catalogItem = clothingCatalogCache[Number(clothingMatch[1])];
            const label = catalogItem ? catalogItem.name : item.id;
            div.innerHTML = `
                <span>👕 ${escapeHtmlAdmin(label)} x${item.amount}</span>
                <button class="wearItemBtn" data-id="${item.id}">Anziehen</button>
            `;
        } else {
            div.innerHTML = `
                <span>${item.name || item.id} x${item.amount}</span>
                <button class="useItemBtn" data-id="${item.id}">Benutzen</button>
            `;
        }

        list.appendChild(div);
    });

    document.querySelectorAll(".useItemBtn").forEach(btn => {
        btn.onclick = () => {
            useItem(btn.dataset.id);
        };
    });
    document.querySelectorAll(".wearItemBtn").forEach(btn => {
        btn.onclick = () => {
            ws.send(JSON.stringify({ type: "wear_clothing", invItemId: btn.dataset.id }));
        };
    });
}

// -------------------------------------------------------------
// RENDERING
// -------------------------------------------------------------
function renderMap() {
    const map = maps[player.world];
    if (!map || !map.tiles) return;

    const cam = getCamera();

    // Nur den sichtbaren Ausschnitt zeichnen (wichtig bei großen Maps)
    const startCol = Math.max(0, Math.floor(cam.x / 32));
    const endCol = Math.min(map.tiles[0].length, startCol + Math.ceil(canvas.width / 32) + 1);
    const startRow = Math.max(0, Math.floor(cam.y / 32));
    const endRow = Math.min(map.tiles.length, startRow + Math.ceil(canvas.height / 32) + 1);

    for (let y = startRow; y < endRow; y++) {
        for (let x = startCol; x < endCol; x++) {
            const tileId = map.tiles[y][x];
            const tile = tileConfig[tileId];

            if (!tile) continue;

            const img = tileImageCache[tileId];
            const rot = (map.tileRot && map.tileRot[y] && map.tileRot[y][x]) || 0;

            if (img && img.complete && img.naturalWidth > 0) {
                const px = x * 32 - cam.x;
                const py = y * 32 - cam.y;

                if (rot !== 0) {
                    ctx.save();
                    ctx.translate(px + 16, py + 16);
                    ctx.rotate(rot * Math.PI / 180);
                    ctx.drawImage(img, -16, -16, 32, 32);
                    ctx.restore();
                } else {
                    ctx.drawImage(img, px, py, 32, 32);
                }
                continue;
            }

            ctx.fillStyle = tile.color;

            ctx.fillRect(
                x * 32 - cam.x,
                y * 32 - cam.y,
                32,
                32
            );
        }
    }
}

function renderDoors() {
    const map = maps[player.world];
    if (!map || !map.doors) return;

    const cam = getCamera();

    map.doors.forEach(d => {
        ctx.fillStyle = "orange";
        ctx.fillRect(
            d.x - cam.x,
            d.y - cam.y,
            30,
            30
        );
    });
}

function drawNameTag(username, id, px, py, gangTag, gangColor, title) {
    const label = (gangTag ? `[${gangTag}] ` : "") + `${username || "???"} (#${id})`;
    ctx.font = "12px Arial";
    ctx.textAlign = "center";

    // Titel (falls gesetzt): kleine goldene Zeile über dem Namen
    if (title) {
        ctx.font = "10px Arial";
        ctx.fillStyle = "black";
        ctx.fillText(title, px + 11, py - 16);
        ctx.fillStyle = "#f5d90a";
        ctx.fillText(title, px + 10, py - 17);
        ctx.font = "12px Arial";
    }

    // Schatten für bessere Lesbarkeit auf jedem Untergrund
    ctx.fillStyle = "black";
    ctx.fillText(label, px + 11, py - 4);

    ctx.fillStyle = gangColor || "white";
    ctx.fillText(label, px + 10, py - 5);

    ctx.textAlign = "left";
}

function drawPackageIcon(px, py) {
    ctx.font = "14px Arial";
    ctx.textAlign = "center";
    ctx.fillText("📦", px + 10, py - 18);
    ctx.textAlign = "left";
}

// -------------------------------------------------------------
// KLEIDUNGSLADEN
// -------------------------------------------------------------
function openClothingShop(catalog) {
    renderClothingCatalog("shirt", catalog.shirt || []);
    renderClothingCatalog("pants", catalog.pants || []);
    renderClothingCatalog("shoes", catalog.shoes || []);
    renderClothingCatalog("helmet", catalog.helmet || []);
    document.getElementById("clothingBox").style.display = "flex";
}

function openTrailerShop(catalog) {
    const container = document.getElementById("trailerShopCatalog");
    container.innerHTML = "";

    if (!catalog || catalog.length === 0) {
        container.innerHTML = `<p style="color:#999; font-size:13px;">Aktuell ist kein Anhänger-Modell im Angebot.</p>`;
    } else {
        catalog.forEach(item => {
            const row = document.createElement("div");
            row.className = "clothing-catalog-row";
            row.innerHTML = `
                <span style="flex:1;">${escapeHtmlAdmin(item.model)}</span>
                <span style="color:#f5d90a;">${item.price}$</span>
                <button onclick="buyTrailerFromShop('${escapeHtmlAdmin(item.model)}')">Kaufen</button>
            `;
            container.appendChild(row);
        });
    }

    document.getElementById("trailerShopBox").style.display = "flex";
}

function closeTrailerShop() {
    document.getElementById("trailerShopBox").style.display = "none";
}

function buyTrailerFromShop(model) {
    ws.send(JSON.stringify({ type: "trailer_shop_buy", model }));
}

function closeClothingShop() {
    document.getElementById("clothingBox").style.display = "none";
}

function renderClothingCatalog(slot, items) {
    const container = document.getElementById("clothingCatalog_" + slot);
    if (!container) return;
    container.innerHTML = "";

    if (items.length === 0) {
        container.innerHTML = `<div style="color:#666; font-size:12px; padding:6px 0;">Noch nichts im Angebot.</div>`;
        return;
    }

    items.forEach(item => {
        const row = document.createElement("div");
        row.className = "clothing-catalog-row";
        row.innerHTML = `
            <span class="swatch-small" style="background:${item.color};"></span>
            <span style="flex:1;">${escapeHtmlAdmin(item.name)}</span>
            <button onclick="buyClothingItem(${item.id})">${item.price}$</button>
        `;
        container.appendChild(row);
    });
}

function buyClothingItem(itemId) {
    ws.send(JSON.stringify({ type: "buy_clothing_item", itemId }));
}

function unwearClothing(slot) {
    ws.send(JSON.stringify({ type: "unwear_clothing", slot }));
}

const clothingImageCache = {};
function getClothingImage(dataUrl) {
    if (!dataUrl) return null;
    if (!clothingImageCache[dataUrl]) {
        const img = new Image();
        img.src = dataUrl;
        clothingImageCache[dataUrl] = img;
    }
    return clothingImageCache[dataUrl];
}

function drawClothingBand(x, y, w, h, color, imageDataUrl) {
    const img = imageDataUrl ? getClothingImage(imageDataUrl) : null;
    if (img && img.complete && img.naturalWidth > 0) {
        ctx.drawImage(img, x, y, w, h);
    } else {
        ctx.fillStyle = color;
        ctx.fillRect(x, y, w, h);
    }
}

function drawPlayerBody(x, y, shirtColor, pantsColor, shoesColor, skin, shirtImage, pantsImage, shoesImage, skinImage, helmetImage) {
    // Figur besteht aus zwei gestapelten 20x20-Kästen:
    // - oberer Kasten (y-20 bis y): Skin + Helm ("Kopf")
    // - unterer Kasten (y bis y+20): Oberteil/Hose/Schuhe ("Körper")
    // "y" bleibt weiterhin der ursprüngliche Ankerpunkt (Spielerposition/Kollision unverändert)
    const headY = y - 20;

    // Unterer Kasten: Kleidung in drei horizontalen Bändern
    drawClothingBand(x, y, 20, 9, shirtColor || "#3498db", shirtImage);
    drawClothingBand(x, y + 9, 20, 7, pantsColor || "#2c3e50", pantsImage);
    drawClothingBand(x, y + 16, 20, 4, shoesColor || "#1a1a1a", shoesImage);
    ctx.strokeStyle = "black";
    ctx.strokeRect(x, y, 20, 20);

    // Oberer Kasten: Skin, dann Helm (Helm sitzt über allem) - kein Rahmen mehr,
    // damit der gemalte Skin nicht von einer schwarzen Umrandung eingerahmt wird

    if (skinImage) {
        const img = getClothingImage(skinImage);
        if (img && img.complete && img.naturalWidth > 0) {
            ctx.drawImage(img, x, headY, 20, 20);
        }
    } else if (skin && skin !== "none") {
        ctx.font = "18px Arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText(skin, x + 10, headY + 11);
        ctx.textAlign = "left";
        ctx.textBaseline = "alphabetic";
    }

    if (helmetImage) {
        const img = getClothingImage(helmetImage);
        if (img && img.complete && img.naturalWidth > 0) {
            ctx.drawImage(img, x, headY, 20, 20);
        }
    }
}

function renderPlayers() {
    const cam = getCamera();

    // eigenen Spieler nur zeichnen, wenn er nicht gerade im/am Auto sitzt
    if (!drivingCarId && !passengerCarId) {
        drawPlayerBody(player.x - cam.x, player.y - cam.y, player.shirtColor, player.pantsColor, player.shoesColor, player.skin, player.shirtImage, player.pantsImage, player.shoesImage, player.skinImage, player.helmetImage);
        drawNameTag(player.username, player.id, player.x - cam.x, player.y - cam.y - 20, player.gangTag, player.gangColor, player.title);
        if (player.hasPackage) drawPackageIcon(player.x - cam.x, player.y - cam.y - 20);
        if (player.cuffed) drawCuffedIcon(player.x - cam.x, player.y - cam.y - 20);
    }

    otherPlayers.forEach(p => {
        const inCar = cars.some(c => c.driverId === p.id || c.passengerId === p.id);
        if (p.world === player.world && !inCar) {
            drawPlayerBody(p.x - cam.x, p.y - cam.y, p.shirtColor, p.pantsColor, p.shoesColor, p.skin, p.shirtImage, p.pantsImage, p.shoesImage, p.skinImage, p.helmetImage);
            drawNameTag(p.username, p.id, p.x - cam.x, p.y - cam.y - 20, p.gangTag, p.gangColor, p.title);
            if (p.hasPackage) drawPackageIcon(p.x - cam.x, p.y - cam.y - 20);
            if (p.bounty > 0) drawBountyIcon(p.x - cam.x, p.y - cam.y - 20, p.bounty);
            if (p.cuffed) drawCuffedIcon(p.x - cam.x, p.y - cam.y - 20);
        }
    });
}

function drawBountyIcon(px, py, amount) {
    ctx.font = "14px Arial";
    ctx.textAlign = "center";
    ctx.fillText("💀", px, py - 46);
    ctx.font = "bold 11px Arial";
    ctx.fillStyle = "#f5d90a";
    ctx.strokeStyle = "black";
    ctx.lineWidth = 3;
    ctx.strokeText(`${amount}$`, px, py - 56);
    ctx.fillText(`${amount}$`, px, py - 56);
    ctx.textAlign = "left";
}

function drawCuffedIcon(px, py) {
    ctx.font = "13px Arial";
    ctx.textAlign = "center";
    ctx.fillText("🔗", px, py - 34);
    ctx.textAlign = "left";
}

function getDayBrightness() {
    // 1 = heller Tag (Mittag), 0 = tiefste Nacht (Mitternacht)
    return (Math.cos(((gameHour - 12) / 24) * Math.PI * 2) + 1) / 2;
}

function renderObjects() {
    const map = maps[player.world];
    if (!map || !map.objects) return;

    const cam = getCamera();
    const brightness = getDayBrightness();
    const isNight = brightness < 0.35;

    map.objects.forEach(o => {
        const cfg = objectConfig[o.type];
        if (!cfg) return;

        const px = o.x - cam.x;
        const py = o.y - (cfg.height - 32) - cam.y;
        const rot = o.rot || 0;

        ctx.save();
        if (rot !== 0) {
            ctx.translate(px + cfg.width / 2, py + cfg.height / 2);
            ctx.rotate(rot * Math.PI / 180);
            ctx.translate(-cfg.width / 2, -cfg.height / 2);
        } else {
            ctx.translate(px, py);
        }

        // Laternen-Leuchteffekt nachts (unter dem Objekt gezeichnet)
        if (isNight && cfg.glowsAtNight) {
            const glowAlpha = (0.35 - brightness) / 0.35 * 0.5;
            const grad = ctx.createRadialGradient(
                cfg.width / 2, cfg.height / 2, 2,
                cfg.width / 2, cfg.height / 2, 55
            );
            grad.addColorStop(0, `rgba(255, 230, 140, ${Math.max(0, glowAlpha)})`);
            grad.addColorStop(1, "rgba(255, 230, 140, 0)");
            ctx.fillStyle = grad;
            ctx.fillRect(-55, -55, cfg.width + 110, cfg.height + 110);
        }

        ctx.fillStyle = cfg.color;

        if (o.open) {
            // Offenes Tor/Tür: durchsichtig gezeichnet, nur Umriss - man kann durchgehen
            ctx.globalAlpha = 0.25;
            ctx.fillRect(0, 0, cfg.width, cfg.height);
            ctx.globalAlpha = 1;
            ctx.strokeStyle = cfg.color;
            ctx.setLineDash([4, 4]);
            ctx.strokeRect(0, 0, cfg.width, cfg.height);
            ctx.setLineDash([]);
        } else {
            ctx.fillRect(0, 0, cfg.width, cfg.height);
            ctx.strokeStyle = "#fff";
            ctx.lineWidth = 1;
            ctx.strokeRect(0, 0, cfg.width, cfg.height);
        }

        ctx.restore();
    });
}

function renderShops() {
    const cam = getCamera();

    shops.forEach(s => {
        const px = s.x * 32 - cam.x;
        const py = s.y * 32 - cam.y;

        ctx.fillStyle = "yellow";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);
    });
}

function renderATMs() {
    const cam = getCamera();
    atms.forEach(a => {
        const px = a.x * 32 - cam.x;
        const py = a.y * 32 - cam.y;

        ctx.fillStyle = "blue";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);
    });
}

function renderGarages() {
    const cam = getCamera();
    garages.forEach(g => {
        const px = g.x * 32 - cam.x;
        const py = g.y * 32 - cam.y;

        ctx.fillStyle = "green";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);
    });
}

function renderJobcenters() {
    const cam = getCamera();
    jobcenters.forEach(j => {
        const px = j.x * 32 - cam.x;
        const py = j.y * 32 - cam.y;

        ctx.fillStyle = "purple";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);
    });
}

function renderGasStations() {
    const cam = getCamera();
    gasStations.forEach(g => {
        const px = g.x * 32 - cam.x;
        const py = g.y * 32 - cam.y;

        ctx.fillStyle = "#e67e22";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.fillStyle = "white";
        ctx.font = "9px Arial";
        ctx.fillText(g.price.toFixed(2) + "$", px + 1, py + 30);
    });
}

function renderPointOfInterestGroup(list, color, icon) {
    const cam = getCamera();
    list.forEach(o => {
        const px = o.x * 32 - cam.x;
        const py = o.y * 32 - cam.y;

        ctx.fillStyle = color;
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText(icon, px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderClothingShops() {
    renderPointOfInterestGroup(clothingShops, "#e84393", "👕");
    renderPointOfInterestGroup(trailerShops, "#8e6a3d", "🚛");
    renderPointOfInterestGroup(highwayLinks, "#f0c419", "🛣️");
    renderPointOfInterestGroup(blackMarketSpots, "#5a1a4a", "🕶️");
}
function renderInsuranceOffices() {
    renderPointOfInterestGroup(insuranceOffices, "#0984e3", "🛡️");
}
function renderPlateOffices() {
    renderPointOfInterestGroup(plateOffices, "#636e72", "🔖");
}

function renderRepairShops() {
    const cam = getCamera();
    repairShops.forEach(r => {
        const px = r.x * 32 - cam.x;
        const py = r.y * 32 - cam.y;

        ctx.fillStyle = "#7f8c8d";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.fillStyle = "white";
        ctx.font = "16px Arial";
        ctx.fillText("🔧", px + 6, py + 22);
    });
}

function renderHouses() {
    const cam = getCamera();
    houses.forEach(h => {
        const px = h.x * 32 - cam.x;
        const py = h.y * 32 - cam.y;

        ctx.fillStyle = h.owned ? "#8e6b4a" : "#c9a876";
        ctx.fillRect(px, py, 32, 32);

        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.fillStyle = "white";
        ctx.font = "16px Arial";
        ctx.fillText("🏠", px + 6, py + 22);
    });
}

function renderJobPoints() {
    const cam = getCamera();
    jobPoints.forEach(jp => {
        const px = jp.x * 32 - cam.x;
        const py = jp.y * 32 - cam.y;

        ctx.fillStyle = jp.kind === "dropoff" ? "#3498db" : "#2ecc71";
        ctx.beginPath();
        ctx.arc(px + 16, py + 16, 12, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = "black";
        ctx.stroke();

        ctx.fillStyle = "white";
        ctx.font = "12px Arial";
        ctx.textAlign = "center";
        ctx.fillText(jp.kind === "dropoff" ? "📥" : "📤", px + 16, py + 20);
        ctx.textAlign = "left";

        ctx.fillStyle = "black";
        ctx.font = "9px Arial";
        ctx.fillText(jp.name, px, py + 34);
    });
}

function renderTaxiStands() {
    const cam = getCamera();
    taxiStands.forEach(t => {
        const px = t.x * 32 - cam.x;
        const py = t.y * 32 - cam.y;

        ctx.fillStyle = "#f5d90a";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🚕", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderHospitals() {
    const cam = getCamera();
    hospitals.forEach(h => {
        const px = h.x * 32 - cam.x;
        const py = h.y * 32 - cam.y;

        ctx.fillStyle = "#e74c3c";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🏥", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderPrisons() {
    const cam = getCamera();
    prisons.forEach(pr => {
        const px = pr.x * 32 - cam.x;
        const py = pr.y * 32 - cam.y;

        ctx.fillStyle = "#555555";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🔒", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderImpoundLots() {
    const cam = getCamera();
    impoundLots.forEach(l => {
        const px = l.x * 32 - cam.x;
        const py = l.y * 32 - cam.y;

        ctx.fillStyle = "#b8860b";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🚛", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderFireStations() {
    const cam = getCamera();
    fireStations.forEach(s => {
        const px = s.x * 32 - cam.x;
        const py = s.y * 32 - cam.y;

        ctx.fillStyle = "#c0392b";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🚒", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderHarvestSpots() {
    const cam = getCamera();
    harvestSpots.forEach(s => {
        const px = s.x * 32 - cam.x;
        const py = s.y * 32 - cam.y;

        ctx.fillStyle = "#2ecc71";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("🌿", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderProcessSpots() {
    const cam = getCamera();
    processSpots.forEach(s => {
        const px = s.x * 32 - cam.x;
        const py = s.y * 32 - cam.y;

        ctx.fillStyle = "#8e44ad";
        ctx.fillRect(px, py, 32, 32);
        ctx.strokeStyle = "black";
        ctx.strokeRect(px, py, 32, 32);

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText("⚗️", px + 16, py + 22);
        ctx.textAlign = "left";
    });
}

function renderDealerSpots() {
    const cam = getCamera();
    const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 200);

    dealerSpots.forEach(s => {
        const px = s.x * 32 - cam.x;
        const py = s.y * 32 - cam.y;

        ctx.strokeStyle = `rgba(230, 126, 34, ${0.4 + pulse * 0.3})`;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(px + 16, py + 16, 22, 0, Math.PI * 2);
        ctx.stroke();

        ctx.fillStyle = "#e67e22";
        ctx.beginPath();
        ctx.arc(px + 16, py + 16, 14, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = "black";
        ctx.stroke();

        ctx.font = "14px Arial";
        ctx.textAlign = "center";
        ctx.fillText("💰", px + 16, py + 21);

        ctx.font = "9px Arial";
        ctx.fillStyle = "white";
        ctx.fillText(`${s.drugName} - ${s.price}$`, px + 16, py + 40);
        ctx.textAlign = "left";
    });
}

function renderFires() {
    if (fires.length === 0) return;

    const cam = getCamera();
    const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 120);

    fires.forEach(f => {
        const px = f.x - cam.x;
        const py = f.y - cam.y;

        // Gefahrenradius andeuten
        ctx.strokeStyle = `rgba(255, 100, 0, ${0.3 + pulse * 0.3})`;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(px, py, 70, 0, Math.PI * 2);
        ctx.stroke();

        ctx.font = `${22 + pulse * 6}px Arial`;
        ctx.textAlign = "center";
        ctx.fillText("🔥", px, py + 8);

        ctx.font = "10px Arial";
        ctx.fillStyle = "white";
        ctx.fillText(`${f.intensity}%`, px, py + 26);
        ctx.textAlign = "left";
    });
}

function renderGroundDrops() {
    if (groundDrops.length === 0) return;

    const cam = getCamera();
    const pulse = 0.5 + 0.5 * Math.sin(Date.now() / 200);

    groundDrops.forEach(d => {
        const px = d.x - cam.x;
        const py = d.y - cam.y;

        ctx.strokeStyle = `rgba(139, 90, 43, ${0.4 + pulse * 0.3})`;
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.arc(px, py, 18, 0, Math.PI * 2);
        ctx.stroke();

        ctx.font = `${16 + pulse * 4}px Arial`;
        ctx.textAlign = "center";
        ctx.fillText("📦", px, py + 6);

        ctx.font = "9px Arial";
        ctx.fillStyle = "white";
        ctx.strokeStyle = "black";
        ctx.lineWidth = 3;
        ctx.strokeText(d.label, px, py - 14);
        ctx.fillText(d.label, px, py - 14);
        ctx.textAlign = "left";
    });
}

function renderTerritoryZones() {
    const cam = getCamera();
    territoryZones.forEach(z => {
        const cx = z.x * 32 - cam.x + 16;
        const cy = z.y * 32 - cam.y + 16;
        const radius = 150;
        const color = z.ownerColor || "#888888";

        ctx.fillStyle = color + "22"; // sehr transparent
        ctx.beginPath();
        ctx.arc(cx, cy, radius, 0, Math.PI * 2);
        ctx.fill();

        ctx.strokeStyle = color;
        ctx.lineWidth = 2;
        ctx.setLineDash(z.ownerTag ? [] : [8, 6]);
        ctx.stroke();
        ctx.setLineDash([]);

        ctx.font = "12px Arial";
        ctx.textAlign = "center";
        ctx.fillStyle = "white";
        const label = z.ownerTag ? `${z.name} [${z.ownerTag}]` : `${z.name} (frei)`;
        ctx.fillText(label, cx, cy - radius - 6);
        ctx.textAlign = "left";
    });
}

// AUTOS (NEU)
function getUsernameById(id) {
    if (id === player.id) return player.username;
    const p = otherPlayers.find(p => p.id === id);
    return p ? p.username : null;
}

function renderCars() {
    const cam = getCamera();
    const blinkOn = Math.floor(Date.now() / 400) % 2 === 0;

    cars.forEach(c => {
        if (c.world !== player.world) return;
        const cfg = carConfigs[c.model] || carConfigs.sedan || { width: 40, height: 22, color: "#c0392b" };
        const w = cfg.width;
        const h = cfg.height;

        ctx.save();
        ctx.translate(c.x - cam.x, c.y - cam.y);
        ctx.rotate(c.angle || 0);

        // Karosserie
        const carImg = carImageCache[c.model];
        if (carImg && carImg.complete && carImg.naturalWidth > 0) {
            ctx.drawImage(carImg, -w / 2, -h / 2, w, h);
            ctx.strokeStyle = "black";
            ctx.strokeRect(-w / 2, -h / 2, w, h);
        } else {
            ctx.fillStyle = c.paintColor || cfg.color;
            ctx.fillRect(-w / 2, -h / 2, w, h);
            ctx.strokeStyle = "black";
            ctx.strokeRect(-w / 2, -h / 2, w, h);
        }

        // Front-Markierung: Fahrtrichtung ist immer +x (rechte Seite vor der Rotation) -
        // kleines helles Dreieck zeigt eindeutig, wo "vorne" ist
        ctx.fillStyle = "#eee";
        ctx.beginPath();
        ctx.moveTo(w / 2, -4);
        ctx.lineTo(w / 2 + 6, 0);
        ctx.lineTo(w / 2, 4);
        ctx.closePath();
        ctx.fill();
        ctx.strokeStyle = "#555";
        ctx.stroke();

        // Scheinwerfer vorne (wenn eingeschaltet)
        if (c.headlights) {
            ctx.fillStyle = "#fff6b0";
            ctx.beginPath();
            ctx.arc(w / 2 - 2, -h / 2 + 3, 2.5, 0, Math.PI * 2);
            ctx.fill();
            ctx.beginPath();
            ctx.arc(w / 2 - 2, h / 2 - 3, 2.5, 0, Math.PI * 2);
            ctx.fill();
        }

        // Bremslicht hinten (wenn gebremst wird)
        if (c.brakeLight) {
            ctx.fillStyle = "red";
            ctx.fillRect(-w / 2, -h / 2, 3, h);
        }

        // Blinker (blinkend, links = beide Ecken oben, rechts = beide Ecken unten, Warnblinker = beide Seiten)
        if (blinkOn) {
            ctx.fillStyle = "orange";
            if (c.leftBlinker || c.hazard) {
                ctx.beginPath();
                ctx.arc(w / 2 - 3, -h / 2 + 1, 2, 0, Math.PI * 2);
                ctx.fill();
                ctx.beginPath();
                ctx.arc(-w / 2 + 3, -h / 2 + 1, 2, 0, Math.PI * 2);
                ctx.fill();
            }
            if (c.rightBlinker || c.hazard) {
                ctx.beginPath();
                ctx.arc(w / 2 - 3, h / 2 - 1, 2, 0, Math.PI * 2);
                ctx.fill();
                ctx.beginPath();
                ctx.arc(-w / 2 + 3, h / 2 - 1, 2, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        ctx.restore();

        // Nummernschild - unrotiert unter dem Auto, damit es immer lesbar ist
        if (c.plate) {
            const py = c.y - cam.y + h / 2 + 11;
            const px = c.x - cam.x;
            ctx.font = "bold 9px monospace";
            ctx.textAlign = "center";
            const textWidth = ctx.measureText(c.plate).width;
            ctx.fillStyle = "rgba(255,255,255,0.9)";
            ctx.fillRect(px - textWidth / 2 - 3, py - 8, textWidth + 6, 11);
            ctx.strokeStyle = "black";
            ctx.lineWidth = 1;
            ctx.strokeRect(px - textWidth / 2 - 3, py - 8, textWidth + 6, 11);
            ctx.fillStyle = "black";
            ctx.fillText(c.plate, px, py);
            ctx.textAlign = "left";
        }

        // Blaulicht auf dem Dach (nur Job-Fahrzeuge, wenn eingeschaltet) - unrotiert,
        // damit es unabhängig von der Fahrtrichtung sichtbar rotiert/blinkt
        if (c.jobId && c.emergency) {
            const flashRed = Math.floor(Date.now() / 200) % 2 === 0;
            ctx.fillStyle = flashRed ? "#ff2b2b" : "#2b6bff";
            ctx.beginPath();
            ctx.arc(c.x - cam.x, c.y - cam.y, 5, 0, Math.PI * 2);
            ctx.fill();
            ctx.strokeStyle = "black";
            ctx.lineWidth = 1;
            ctx.stroke();
        }

        if (c.driverId) {
            const name = getUsernameById(c.driverId);
            if (name) drawNameTag(name, c.driverId, c.x - cam.x - w / 2, c.y - cam.y - h / 2);
        }
        if (c.passengerId) {
            const name = getUsernameById(c.passengerId);
            if (name) drawNameTag("🧑 " + name, c.passengerId, c.x - cam.x - w / 2, c.y - cam.y + h / 2 + 12);
        }

        // Rauch bei starkem Schaden (unter 40% Zustand)
        if ((c.health ?? 100) < 40) {
            const smokeAlpha = 0.15 + Math.random() * 0.15;
            ctx.fillStyle = `rgba(80,80,80,${smokeAlpha})`;
            ctx.beginPath();
            ctx.arc(c.x - cam.x, c.y - cam.y - 10 - Math.random() * 6, 5 + Math.random() * 3, 0, Math.PI * 2);
            ctx.fill();
        }
    });
}

// -------------------------------------------------------------
// MINI-MAP / RADAR (NEU)
// -------------------------------------------------------------
const minimapCanvas = document.getElementById("minimapCanvas");
const minimapCtx = minimapCanvas ? minimapCanvas.getContext("2d") : null;
const MINIMAP_RANGE = 500; // sichtbarer Radius in Pixel-Weltkoordinaten

function renderMinimap() {
    if (!minimapCtx) return;

    const size = minimapCanvas.width; // quadratisch
    const scale = (size / 2) / MINIMAP_RANGE;

    minimapCtx.clearRect(0, 0, size, size);

    // Kreisförmige Maske
    minimapCtx.save();
    minimapCtx.beginPath();
    minimapCtx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
    minimapCtx.clip();

    minimapCtx.fillStyle = "#0a0a0a";
    minimapCtx.fillRect(0, 0, size, size);

    function toMini(wx, wy) {
        return {
            x: size / 2 + (wx - player.x) * scale,
            y: size / 2 + (wy - player.y) * scale
        };
    }

    function drawDot(wx, wy, color, r) {
        const p2 = toMini(wx, wy);
        if (p2.x < 0 || p2.x > size || p2.y < 0 || p2.y > size) return;
        minimapCtx.fillStyle = color;
        minimapCtx.beginPath();
        minimapCtx.arc(p2.x, p2.y, r, 0, Math.PI * 2);
        minimapCtx.fill();
    }

    // POIs
    shops.forEach(s => drawDot(s.x * 32 + 16, s.y * 32 + 16, "yellow", 3));
    garages.forEach(g => drawDot(g.x * 32 + 16, g.y * 32 + 16, "#2ecc71", 3));
    jobcenters.forEach(j => drawDot(j.x * 32 + 16, j.y * 32 + 16, "purple", 3));
    gasStations.forEach(g => drawDot(g.x * 32 + 16, g.y * 32 + 16, "#e67e22", 3));
    clothingShops.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#e84393", 3));
    trailerShops.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#8e6a3d", 3));
    highwayLinks.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#f0c419", 3));
    blackMarketSpots.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#5a1a4a", 3));
    insuranceOffices.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#0984e3", 3));
    plateOffices.forEach(o => drawDot(o.x * 32 + 16, o.y * 32 + 16, "#636e72", 3));
    repairShops.forEach(r => drawDot(r.x * 32 + 16, r.y * 32 + 16, "#7f8c8d", 3));
    houses.forEach(h => drawDot(h.x * 32 + 16, h.y * 32 + 16, "#c9a876", 3));
    jobPoints.forEach(jp => drawDot(jp.x * 32 + 16, jp.y * 32 + 16, jp.kind === "dropoff" ? "#3498db" : "#2ecc71", 3.5));
    taxiStands.forEach(t => drawDot(t.x * 32 + 16, t.y * 32 + 16, "#f5d90a", 3.5));
    hospitals.forEach(h => drawDot(h.x * 32 + 16, h.y * 32 + 16, "#e74c3c", 3.5));
    prisons.forEach(pr => drawDot(pr.x * 32 + 16, pr.y * 32 + 16, "#7f7f7f", 3.5));
    impoundLots.forEach(l => drawDot(l.x * 32 + 16, l.y * 32 + 16, "#b8860b", 3.5));
    fireStations.forEach(s => drawDot(s.x * 32 + 16, s.y * 32 + 16, "#c0392b", 3.5));
    harvestSpots.forEach(s => drawDot(s.x * 32 + 16, s.y * 32 + 16, "#2ecc71", 3.5));
    processSpots.forEach(s => drawDot(s.x * 32 + 16, s.y * 32 + 16, "#8e44ad", 3.5));
    dealerSpots.forEach(s => drawDot(s.x * 32 + 16, s.y * 32 + 16, "#e67e22", 4.5));
    fires.forEach(f => drawDot(f.x, f.y, "#ff5500", 5));
    groundDrops.forEach(d => drawDot(d.x, d.y, "#8b5a2b", 4));
    territoryZones.forEach(z => drawDot(z.x * 32 + 16, z.y * 32 + 16, z.ownerColor || "#888888", 4));
    crimeAlerts.forEach(a => {
        if (a.world === player.world) drawDot(a.x, a.y, a.kind === "medic" ? "#2ecc71" : "#ff2828", 4.5);
    });

    // Andere Spieler
    otherPlayers.forEach(p => {
        if (p.world === player.world) drawDot(p.x, p.y, p.bounty > 0 ? "#f5d90a" : (p.color || "#3498db"), p.bounty > 0 ? 4 : 3);
    });

    // Autos
    cars.forEach(c => {
        if (c.world === player.world) drawDot(c.x, c.y, "#c0392b", 2.5);
    });

    minimapCtx.restore();

    // Rahmen
    minimapCtx.strokeStyle = "#555";
    minimapCtx.lineWidth = 2;
    minimapCtx.beginPath();
    minimapCtx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
    minimapCtx.stroke();

    // Spieler selbst (immer im Zentrum, als kleines Dreieck in Blickrichtung - hier simpel als Punkt)
    minimapCtx.fillStyle = "yellow";
    minimapCtx.beginPath();
    minimapCtx.arc(size / 2, size / 2, 4, 0, Math.PI * 2);
    minimapCtx.fill();
    minimapCtx.strokeStyle = "black";
    minimapCtx.lineWidth = 1;
    minimapCtx.stroke();
}

function renderDayNightOverlay() {
    const brightness = getDayBrightness();
    const maxDarkness = 0.62;
    const alpha = (1 - brightness) * maxDarkness;

    if (alpha > 0.01) {
        ctx.fillStyle = `rgba(10, 10, 40, ${alpha})`;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
}

function renderRain() {
    if (weather !== "rain") return;

    ctx.strokeStyle = "rgba(180, 200, 255, 0.5)";
    ctx.lineWidth = 1;

    const now = Date.now() / 1000;
    rainDrops.forEach(d => {
        const x = d.x * canvas.width;
        const y = ((d.y * canvas.height) + now * d.speed * 40) % canvas.height;
        ctx.beginPath();
        ctx.moveTo(x, y);
        ctx.lineTo(x - 3, y + 12);
        ctx.stroke();
    });
}

function renderCrimeAlerts() {
    const now = Date.now();
    crimeAlerts = crimeAlerts.filter(a => a.expiresAt > now);
    if (crimeAlerts.length === 0) return;

    const cam = getCamera();
    const pulse = 0.5 + 0.5 * Math.sin(now / 150);

    crimeAlerts.forEach(a => {
        if (a.world !== player.world) return;
        const px = a.x - cam.x;
        const py = a.y - cam.y;
        const isMedic = a.kind === "medic";

        ctx.strokeStyle = isMedic
            ? `rgba(46, 204, 113, ${0.5 + pulse * 0.5})`
            : `rgba(255, 40, 40, ${0.5 + pulse * 0.5})`;
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(px, py, 16 + pulse * 6, 0, Math.PI * 2);
        ctx.stroke();

        ctx.font = "16px Arial";
        ctx.textAlign = "center";
        ctx.fillText(isMedic ? "⛑️" : "🚨", px, py + 5);
        ctx.textAlign = "left";
    });
}

function render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    renderMap();
    renderObjects();
    renderShops();
    renderATMs();
    renderGarages();
    renderJobcenters();
    renderGasStations();
    renderClothingShops();
    renderInsuranceOffices();
    renderPlateOffices();
    renderRepairShops();
    renderHouses();
    renderJobPoints();
    renderTaxiStands();
    renderHospitals();
    renderPrisons();
    renderImpoundLots();
    renderFireStations();
    renderHarvestSpots();
    renderProcessSpots();
    renderDealerSpots();
    renderFires();
    renderGroundDrops();
    renderTerritoryZones();
    renderCars();
    renderDoors();
    renderPlayers();
    renderDayNightOverlay();
    renderRain();
    renderCrimeAlerts();
    renderMinimap();
    if (naviTarget) updateNaviDisplay();

    requestAnimationFrame(render);
}

// -------------------------------------------------------------
// GAME LOOP
// -------------------------------------------------------------
function gameLoop() {
    pollGamepad();
    updateMovement();
    setTimeout(gameLoop, 16);
    updateStatusWindow();
    updateDebugBox();
}

render();
gameLoop();

// -------------------------------------------------------------
// AUTO-LOGIN: nur noch über die Startseite index.html möglich - hier
// wird ausschließlich ein bereits vorhandener Token übernommen, ohne
// eigenes Login-Formular auf der Spielseite
// -------------------------------------------------------------
const storedToken = localStorage.getItem("token");
if (storedToken) {
    loginBox.style.display = "none";
    canvas.style.display = "block";
    showLoadingScreen();
    startMultiplayer(storedToken);
} else {
    location.href = "/index.html";
}

// -------------------------------------------------------------
// MOBILE TOUCH-STEUERUNG: virtueller Joystick
// -------------------------------------------------------------
if (isTouchDevice) {
    const joyBase = document.getElementById("touchJoystickBase");
    const joyKnob = document.getElementById("touchJoystickKnob");
    const JOY_RADIUS = 45; // maximaler Weg des Knopfs vom Zentrum, in px
    const JOY_DEADZONE = 12;
    let joyTouchId = null;

    function resetJoystick() {
        touchMove.w = touchMove.a = touchMove.s = touchMove.d = false;
        joyKnob.style.transform = "translate(0px, 0px)";
    }

    function updateJoystickFromTouch(touch) {
        const rect = joyBase.getBoundingClientRect();
        const centerX = rect.left + rect.width / 2;
        const centerY = rect.top + rect.height / 2;

        let dx = touch.clientX - centerX;
        let dy = touch.clientY - centerY;
        const dist = Math.hypot(dx, dy);

        if (dist > JOY_RADIUS) {
            dx = (dx / dist) * JOY_RADIUS;
            dy = (dy / dist) * JOY_RADIUS;
        }
        joyKnob.style.transform = `translate(${dx}px, ${dy}px)`;

        touchMove.w = dy < -JOY_DEADZONE;
        touchMove.s = dy > JOY_DEADZONE;
        touchMove.a = dx < -JOY_DEADZONE;
        touchMove.d = dx > JOY_DEADZONE;
    }

    joyBase.addEventListener("touchstart", e => {
        e.preventDefault();
        const touch = e.changedTouches[0];
        joyTouchId = touch.identifier;
        updateJoystickFromTouch(touch);
    }, { passive: false });

    joyBase.addEventListener("touchmove", e => {
        e.preventDefault();
        for (const touch of e.changedTouches) {
            if (touch.identifier === joyTouchId) updateJoystickFromTouch(touch);
        }
    }, { passive: false });

    function endJoystickTouch(e) {
        for (const touch of e.changedTouches) {
            if (touch.identifier === joyTouchId) {
                joyTouchId = null;
                resetJoystick();
            }
        }
    }
    joyBase.addEventListener("touchend", endJoystickTouch);
    joyBase.addEventListener("touchcancel", endJoystickTouch);

    // -------------------------------------------------------------
    // MOBILE TOUCH-STEUERUNG: Action-Buttons (simulieren echte Tastendrücke,
    // damit die komplette bestehende Logik inkl. Fenster-Sperre mitgilt)
    // -------------------------------------------------------------
    function simulateKeyPress(key) {
        document.dispatchEvent(new KeyboardEvent("keydown", { key }));
        document.dispatchEvent(new KeyboardEvent("keyup", { key }));
    }

    document.getElementById("touchBtnInteract").addEventListener("touchstart", e => {
        e.preventDefault();
        simulateKeyPress("e");
    }, { passive: false });

    document.getElementById("touchBtnJob").addEventListener("touchstart", e => {
        e.preventDefault();
        simulateKeyPress("j");
    }, { passive: false });

    document.getElementById("touchBtnPhone").addEventListener("touchstart", e => {
        e.preventDefault();
        simulateKeyPress("p");
    }, { passive: false });

    document.getElementById("touchBtnMenu").addEventListener("touchstart", e => {
        e.preventDefault();
        openCommandsWindow();
    }, { passive: false });
}