aether-framework/resources/[aether]/aether-hud/html/index.html
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark,
  Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI)
- aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource,
  an aether-core gekoppelt (Rechte über aether_users.admin_group), mit
  WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU)
- Prototypen admin/ vervollständigt
- SQL-Schema aether_admin.sql
- Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:55:32 +02:00

485 lines
25 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>Aether — HUD</title>
<style>
:root{
--fg:#f2f0fa; /* Standard-Textfarbe */
--muted:#a09bbd; /* gedämpfter Text */
--panel:rgba(14,12,24,0.62); /* halbtransparente HUD-Fläche */
--panel2:rgba(20,17,34,0.9); /* dichtere Panel-Fläche */
--border:rgba(255,255,255,0.1);/* dezente Rahmen */
--accent:#7c5cf6; /* Aether-Akzent (Violett) */
--accent2:#5b8cff; /* zweiter Akzent (Blau) */
/* Statusfarben je Anzeige */
--health:#34d39a; --armor:#5b8cff; --hunger:#f5a742;
--thirst:#3fc7e0; --stamina:#c3e04a; --voice:#b58cff; --fuel:#f5a742;
}
*{box-sizing:border-box;}
html,body{margin:0;height:100%;overflow:hidden;background:transparent;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--fg);}
/* In FiveM ist das HUD ein reines Overlay: Kulisse und Demo-Panel entfallen. */
body.ingame .scene{display:none;}
body.ingame .demo{display:none;}
/* Sanftes Ein-/Ausblenden des gesamten HUDs (z. B. bei offenen Menüs) */
.hud{transition:opacity .2s;}
body.hud-aus .hud{opacity:0;pointer-events:none;}
/* ============================================================
STILISIERTE SPIELSZENE (nur Kulisse, damit das HUD als Overlay wirkt)
============================================================ */
.scene{position:fixed;inset:0;z-index:0;overflow:hidden;
background:linear-gradient(180deg,#1a1440 0%,#3a2a5e 32%,#7a4a6a 52%,#c98a5a 66%,#e0a86a 74%,#2a2038 74%);}
/* Sonnen-/Lichtschein am Horizont */
.scene .sun{position:absolute;left:50%;top:60%;width:340px;height:340px;transform:translate(-50%,-50%);
border-radius:50%;background:radial-gradient(circle,rgba(255,220,150,0.9),rgba(255,180,120,0.2) 45%,transparent 70%);}
/* Häuser-Silhouette im Vordergrund */
.scene .sky{position:absolute;inset:0;background:radial-gradient(1px 1px at 20% 15%,rgba(255,255,255,0.7),transparent),
radial-gradient(1px 1px at 70% 8%,rgba(255,255,255,0.5),transparent),
radial-gradient(1px 1px at 45% 22%,rgba(255,255,255,0.6),transparent),
radial-gradient(1px 1px at 85% 18%,rgba(255,255,255,0.5),transparent);}
.scene .city{position:absolute;left:0;right:0;bottom:26%;height:34%;
background:
linear-gradient(90deg,transparent 4%,#241a38 4% 9%,transparent 9% 13%,#2c2044 13% 20%,transparent 20% 24%,
#201830 24% 30%,transparent 30% 36%,#2c2044 36% 41%,transparent 41% 47%,#281c40 47% 55%,transparent 55% 60%,
#241a38 60% 67%,transparent 67% 72%,#2c2044 72% 80%,transparent 80% 85%,#201830 85% 92%,transparent 92%);
filter:drop-shadow(0 -2px 0 rgba(124,92,246,0.25));}
.scene .ground{position:absolute;left:0;right:0;bottom:0;height:26%;background:linear-gradient(180deg,#1a1428,#0c0a16);}
.scene .road{position:absolute;left:50%;bottom:0;width:60%;height:26%;transform:translateX(-50%);
background:linear-gradient(180deg,#2a2438,#151020);clip-path:polygon(38% 0,62% 0,100% 100%,0 100%);}
.scene .dash{position:absolute;left:50%;bottom:0;width:4px;height:26%;transform:translateX(-50%);
background:repeating-linear-gradient(180deg,rgba(245,167,66,0.8) 0 24px,transparent 24px 48px);}
/* ============================================================
HUD-OVERLAY-CONTAINER
============================================================ */
.hud{position:fixed;inset:0;z-index:2;pointer-events:none;} /* Klicks gehen durch, außer bei Bedienelementen */
.hud > *{pointer-events:auto;}
/* Voice-Anzeige oben mittig */
.voice{position:absolute;top:20px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:10px;
background:var(--panel);border:1px solid var(--border);border-radius:999px;padding:7px 15px;backdrop-filter:blur(10px);}
.voice .mic{font-size:16px;transition:.15s;}
.voice.talking .mic{color:var(--voice);text-shadow:0 0 12px var(--voice);}
.voice .bars{display:flex;align-items:flex-end;gap:3px;height:16px;}
.voice .bars i{width:4px;background:var(--border);border-radius:2px;transition:.15s;}
.voice .bars i.on{background:var(--voice);}
.voice .range{font-size:11.5px;font-weight:800;color:var(--muted);letter-spacing:0.03em;}
.voice.talking .range{color:var(--fg);}
/* Status-Cluster unten links */
.cluster{position:absolute;left:22px;bottom:22px;display:flex;flex-direction:column;gap:12px;align-items:flex-start;}
.gauges{display:flex;gap:9px;flex-wrap:wrap;max-width:340px;}
.gauge{width:44px;height:44px;position:relative;}
.gauge svg{transform:rotate(-90deg);} /* Startpunkt oben */
.gauge .track{stroke:rgba(255,255,255,0.12);}
.gauge .icon{position:absolute;inset:0;display:grid;place-items:center;font-size:16px;}
.gauge .num{position:absolute;left:50%;bottom:-14px;transform:translateX(-50%);font-size:9.5px;font-weight:800;
color:var(--muted);font-variant-numeric:tabular-nums;}
/* Geld-Anzeige unten rechts */
.money{position:absolute;right:22px;bottom:22px;display:flex;flex-direction:column;gap:7px;align-items:flex-end;}
.money .row{display:flex;align-items:center;gap:9px;background:var(--panel);border:1px solid var(--border);
border-radius:11px;padding:7px 13px;backdrop-filter:blur(10px);min-width:160px;justify-content:flex-end;}
.money .row .mi{font-size:14px;}
.money .row .ml{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:0.07em;font-weight:800;margin-right:auto;}
.money .row .mv{font-size:16px;font-weight:800;font-variant-numeric:tabular-nums;}
.money .cash .mv{color:var(--health);}
.money .bank .mv{color:var(--accent2);}
.money .dirty .mv{color:#f5a742;}
.money .row.flash{animation:moneyFlash .6s ease;}
@keyframes moneyFlash{0%{transform:scale(1);}30%{transform:scale(1.06);border-color:var(--accent);}100%{transform:scale(1);}}
/* Fahrzeug-HUD unten rechts (über der Geld-Anzeige) */
.vehicle{position:absolute;right:22px;bottom:130px;display:none;align-items:flex-end;gap:14px;}
.vehicle.on{display:flex;}
.speedo{width:150px;height:150px;position:relative;}
.speedo svg{transform:rotate(135deg);} /* 270°-Skala, unten offen */
.speedo .big{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;}
.speedo .big .n{font-size:38px;font-weight:900;line-height:1;font-variant-numeric:tabular-nums;}
.speedo .big .u{font-size:11px;color:var(--muted);font-weight:800;letter-spacing:0.1em;margin-top:2px;}
.vinfo{display:flex;flex-direction:column;gap:7px;align-items:flex-end;margin-bottom:8px;}
.vinfo .gear{background:var(--panel);border:1px solid var(--border);border-radius:10px;padding:6px 12px;backdrop-filter:blur(10px);
font-size:13px;font-weight:800;}
.vinfo .gear b{color:var(--accent);font-size:16px;}
.warn{display:flex;gap:7px;}
.warn .w{width:34px;height:34px;border-radius:9px;display:grid;place-items:center;font-size:15px;
background:var(--panel);border:1px solid var(--border);backdrop-filter:blur(10px);color:var(--muted);transition:.15s;}
.warn .w.alert{color:#f0506e;border-color:#f0506e;background:rgba(240,80,110,0.14);animation:blink 1s steps(2) infinite;}
.warn .w.ok{color:var(--health);}
@keyframes blink{50%{opacity:0.35;}}
.fuelbar{width:150px;height:6px;border-radius:99px;background:rgba(255,255,255,0.12);overflow:hidden;margin-top:2px;}
.fuelbar i{display:block;height:100%;background:linear-gradient(90deg,#f5a742,#f0d24a);border-radius:99px;transition:width .3s;}
/* ============================================================
DEMO-/DEBUG-PANEL (nur Prototyp — in FiveM entfällt es)
============================================================ */
.demo{position:absolute;top:20px;right:20px;width:280px;background:var(--panel2);border:1px solid var(--border);
border-radius:16px;backdrop-filter:blur(14px);box-shadow:0 24px 50px -20px rgba(0,0,0,0.8);overflow:hidden;}
.demo .dh{display:flex;align-items:center;justify-content:space-between;padding:12px 15px;cursor:pointer;
border-bottom:1px solid var(--border);}
.demo .dh .t{font-size:13px;font-weight:800;} .demo .dh .t small{color:var(--muted);font-weight:600;font-size:10.5px;display:block;margin-top:1px;}
.demo .dh .tog{color:var(--muted);font-size:15px;transition:.2s;}
.demo.collapsed .dh .tog{transform:rotate(-90deg);}
.demo .db{padding:13px 15px;display:flex;flex-direction:column;gap:13px;max-height:76vh;overflow-y:auto;}
.demo.collapsed .db{display:none;}
.demo .grp{display:flex;flex-direction:column;gap:8px;}
.demo .grp > .gl{font-size:9.5px;color:var(--muted);text-transform:uppercase;letter-spacing:0.08em;font-weight:800;}
.demo .slider{display:flex;align-items:center;gap:9px;}
.demo .slider label{font-size:11.5px;width:64px;color:var(--fg);font-weight:600;}
.demo .slider input[type=range]{flex:1;accent-color:var(--accent);cursor:pointer;}
.demo .slider .rv{font-size:11px;color:var(--muted);width:30px;text-align:right;font-variant-numeric:tabular-nums;}
.demo .btns{display:flex;flex-wrap:wrap;gap:6px;}
.demo button{border:1px solid var(--border);background:rgba(255,255,255,0.05);color:var(--fg);border-radius:9px;
padding:7px 10px;font-family:inherit;font-size:11.5px;font-weight:700;cursor:pointer;transition:.12s;}
.demo button:hover{border-color:var(--accent);color:#fff;}
.demo button.pri{background:var(--accent);color:#160c2e;border-color:transparent;}
.demo button.on{background:var(--accent-soft,rgba(124,92,246,0.2));border-color:var(--accent);color:#fff;}
.demo .divider{height:1px;background:var(--border);margin:1px 0;}
</style>
</head>
<body>
<!-- Kulisse hinter dem HUD -->
<div class="scene">
<div class="sky"></div><div class="sun"></div><div class="city"></div>
<div class="ground"></div><div class="road"></div><div class="dash"></div>
</div>
<!-- HUD-Overlay -->
<div class="hud">
<!-- Voice-Anzeige -->
<div class="voice" id="voice">
<span class="mic">🎙️</span>
<div class="bars"><i style="height:6px"></i><i style="height:10px"></i><i style="height:14px"></i></div>
<span class="range" id="voiceRange">Normal</span>
</div>
<!-- Status-Cluster -->
<div class="cluster">
<div class="gauges" id="gauges"></div>
</div>
<!-- Fahrzeug-HUD -->
<div class="vehicle" id="vehicle">
<div class="vinfo">
<div class="gear">Gang <b id="gear">N</b></div>
<div class="warn">
<div class="w" id="wSeat" title="Sicherheitsgurt">🎗️</div>
<div class="w ok" id="wEngine" title="Motor">⚙️</div>
</div>
<div class="fuelbar"><i id="fuelFill"></i></div>
</div>
<div class="speedo">
<svg width="150" height="150" viewBox="0 0 150 150">
<circle class="track" cx="75" cy="75" r="62" fill="none" stroke-width="10" stroke-linecap="round"
stroke-dasharray="292 390" pathLength="390"></circle>
<circle id="speedArc" cx="75" cy="75" r="62" fill="none" stroke="url(#sg)" stroke-width="10" stroke-linecap="round"
stroke-dasharray="0 390" pathLength="390"></circle>
<defs><linearGradient id="sg"><stop offset="0" stop-color="#5b8cff"/><stop offset="1" stop-color="#7c5cf6"/></linearGradient></defs>
</svg>
<div class="big"><div class="n" id="speedN">0</div><div class="u">KM/H</div></div>
</div>
</div>
<!-- Geld-Anzeige -->
<div class="money" id="money"></div>
<!-- Demo-Panel -->
<div class="demo" id="demo">
<div class="dh" id="demoHead">
<div class="t">Demo-Steuerung<small>Nur Prototyp — Werte simulieren</small></div>
<div class="tog">▾</div>
</div>
<div class="db">
<div class="grp">
<div class="gl">Status (0–100)</div>
<div class="slider"><label>❤️ Leben</label><input type="range" id="sLeben" min="0" max="100" value="100"><span class="rv" id="rLeben">100</span></div>
<div class="slider"><label>🍔 Hunger</label><input type="range" id="sHunger" min="0" max="100" value="82"><span class="rv" id="rHunger">82</span></div>
<div class="slider"><label>💧 Durst</label><input type="range" id="sDurst" min="0" max="100" value="74"><span class="rv" id="rDurst">74</span></div>
<div class="slider"><label>🫁 Ausdauer</label><input type="range" id="sAusdauer" min="0" max="100" value="100"><span class="rv" id="rAusdauer">100</span></div>
</div>
<div class="divider"></div>
<div class="grp">
<div class="gl">Aktionen</div>
<div class="btns">
<button id="bDamage">Schaden −20</button>
<button id="bHeal">Heilen</button>
<button id="bEat">🍔 Essen</button>
<button id="bDrink">💧 Trinken</button>
<button id="bTalk">🎙️ Sprechen</button>
<button id="bVoice">Voice-Reichweite</button>
</div>
</div>
<div class="divider"></div>
<div class="grp">
<div class="gl">Geld</div>
<div class="btns">
<button id="bCash">Bargeld +500</button>
<button id="bBank">Konto −250</button>
<button id="bDirty">Schwarzgeld an/aus</button>
</div>
</div>
<div class="divider"></div>
<div class="grp">
<div class="gl">Fahrzeug</div>
<div class="btns"><button id="bVehicle" class="pri">🚗 Einsteigen</button></div>
<div class="slider"><label>Tempo</label><input type="range" id="sSpeed" min="0" max="260" value="0"><span class="rv" id="rSpeed">0</span></div>
<div class="slider"><label>Tank</label><input type="range" id="sFuel" min="0" max="100" value="68"><span class="rv" id="rFuel">68</span></div>
<div class="btns">
<button id="bSeat">🎗️ Gurt an/aus</button>
<button id="bEngine" class="on">⚙️ Motor an</button>
</div>
</div>
</div>
</div>
</div>
<script>
"use strict";
/* Kurzer Zugriffshelfer auf Elemente per ID */
const $=id=>document.getElementById(id);
/* Zahl auf einen Bereich begrenzen */
const clamp=(n,a,b)=>Math.max(a,Math.min(b,n));
/* Betrag als Euro formatieren */
const euro=n=>Math.round(n).toLocaleString("de-DE")+" €";
/* =====================================================================
HUD-ZUSTAND (im echten Betrieb per NUI vom Client gesetzt)
Bewusst vom UI getrennt gehalten.
===================================================================== */
const hud={
leben:100, hunger:82, durst:74, ausdauer:100, // Status 0–100
redet:false, // spricht der Spieler gerade?
voiceStufe:1, // 0=Flüstern,1=Normal,2=Rufen
bargeld:2450, konto:18900, schwarzgeld:1200, // Geldwerte
schwarzgeldSichtbar:false, // Schwarzgeld nur bei Bedarf zeigen
// Fahrzeug-bezogene Werte
imFahrzeug:false, tempo:0, tank:68, gurt:false, motor:true,
};
/* Die drei Voice-Reichweiten mit Beschriftung und Anzahl aktiver Balken */
const voiceStufen=[{name:"Flüstern",bars:1},{name:"Normal",bars:2},{name:"Rufen",bars:3}];
/* Definition der Status-Anzeigen (Reihenfolge = Anzeige-Reihenfolge) */
const gaugeDefs=[
{key:"leben", icon:"❤️", farbe:"var(--health)"},
{key:"hunger", icon:"🍔", farbe:"var(--hunger)"},
{key:"durst", icon:"💧", farbe:"var(--thirst)"},
{key:"ausdauer", icon:"🫁", farbe:"var(--stamina)"},
];
const RING_UMFANG=2*Math.PI*17; // Umfang der Status-Ringe (Radius 17)
/* =====================================================================
RENDERING
===================================================================== */
/* Baut die Status-Ringe einmalig auf (SVG-Ring + Icon + Zahl) */
function baueGauges(){
$("gauges").innerHTML=gaugeDefs.map(g=>
`<div class="gauge" title="${g.key}">
<svg width="44" height="44" viewBox="0 0 44 44">
<circle class="track" cx="22" cy="22" r="17" fill="none" stroke-width="4"></circle>
<circle id="arc-${g.key}" cx="22" cy="22" r="17" fill="none" stroke="${g.farbe}" stroke-width="4"
stroke-linecap="round" stroke-dasharray="${RING_UMFANG}" stroke-dashoffset="${RING_UMFANG}"></circle>
</svg>
<div class="icon">${g.icon}</div>
<div class="num" id="num-${g.key}">0</div>
</div>`).join('');
}
/* Aktualisiert die Füllstände aller Status-Ringe aus dem Zustand */
function renderGauges(){
for(const g of gaugeDefs){
const wert=clamp(hud[g.key],0,100);
const arc=$("arc-"+g.key);
arc.style.strokeDashoffset=RING_UMFANG*(1-wert/100); // weniger Wert = mehr Lücke
$("num-"+g.key).textContent=Math.round(wert);
}
}
/* Aktualisiert die Voice-Anzeige (Reichweite + Sprech-Zustand) */
function renderVoice(){
const stufe=voiceStufen[hud.voiceStufe];
$("voiceRange").textContent=stufe.name;
$("voice").classList.toggle("talking",hud.redet);
// Nur so viele Balken hervorheben, wie die Reichweite vorgibt (und nur beim Reden)
$("voice").querySelectorAll(".bars i").forEach((b,i)=>
b.classList.toggle("on",hud.redet&&i<stufe.bars));
}
/* Aktualisiert die Geld-Anzeige (Schwarzgeld optional) */
function renderMoney(){
let html=
`<div class="row cash"><span class="mi">💵</span><span class="ml">Bargeld</span><span class="mv">${euro(hud.bargeld)}</span></div>
<div class="row bank"><span class="mi">🏦</span><span class="ml">Konto</span><span class="mv">${euro(hud.konto)}</span></div>`;
if(hud.schwarzgeldSichtbar)
html+=`<div class="row dirty"><span class="mi">🕶️</span><span class="ml">Schwarzgeld</span><span class="mv">${euro(hud.schwarzgeld)}</span></div>`;
$("money").innerHTML=html;
}
/* Lässt eine Geld-Zeile kurz aufblinken, wenn sich der Wert ändert */
function blinkeGeld(klasse){const el=$("money").querySelector("."+klasse);if(el){el.classList.add("flash");setTimeout(()=>el.classList.remove("flash"),600);}}
/* Aktualisiert das Fahrzeug-HUD (Tacho, Gang, Tank, Warnungen) */
const SPEED_MAX=260; // Skalen-Maximum des Tachos
const SPEED_PATHLEN=390, SPEED_ARC=292; // Pfad-Länge und sichtbarer 270°-Bogen
function renderVehicle(){
$("vehicle").classList.toggle("on",hud.imFahrzeug);
if(!hud.imFahrzeug)return;
// Tacho-Bogen proportional zum Tempo füllen
const anteil=clamp(hud.tempo/SPEED_MAX,0,1);
$("speedArc").setAttribute("stroke-dasharray",(SPEED_ARC*anteil)+" "+SPEED_PATHLEN);
$("speedN").textContent=Math.round(hud.tempo);
// Gang aus dem Tempo ableiten (grobe Simulation)
$("gear").textContent=!hud.motor?"–":hud.tempo<1?"N":Math.min(6,Math.floor(hud.tempo/38)+1);
// Tankanzeige
$("fuelFill").style.width=clamp(hud.tank,0,100)+"%";
// Warnleuchten: Gurt blinkt bei Fahrt ohne Gurt, Motor zeigt Status
$("wSeat").className="w"+(!hud.gurt&&hud.tempo>5?" alert":(hud.gurt?" ok":""));
$("wEngine").className="w"+(hud.motor?" ok":" alert");
}
/* Rendert das komplette HUD neu */
function renderAll(){renderGauges();renderVoice();renderMoney();renderVehicle();}
/* =====================================================================
DEMO-STEUERUNG (nur im Prototyp; in FiveM ersetzt durch echte Events)
===================================================================== */
/* Verknüpft einen Slider mit einem Zustandswert inkl. Zahl-Anzeige */
function bindeSlider(sliderId,anzeigeId,setter){
const s=$(sliderId);
s.addEventListener("input",()=>{$(anzeigeId).textContent=s.value;setter(+s.value);renderAll();});
}
bindeSlider("sLeben","rLeben",v=>hud.leben=v);
bindeSlider("sHunger","rHunger",v=>hud.hunger=v);
bindeSlider("sDurst","rDurst",v=>hud.durst=v);
bindeSlider("sAusdauer","rAusdauer",v=>hud.ausdauer=v);
bindeSlider("sSpeed","rSpeed",v=>hud.tempo=v);
bindeSlider("sFuel","rFuel",v=>hud.tank=v);
/* Synchronisiert die Slider-Positionen mit dem Zustand (nach Aktionen) */
function syncSlider(){
$("sLeben").value=hud.leben; $("rLeben").textContent=Math.round(hud.leben);
$("sHunger").value=hud.hunger; $("rHunger").textContent=Math.round(hud.hunger);
$("sDurst").value=hud.durst; $("rDurst").textContent=Math.round(hud.durst);
$("sAusdauer").value=hud.ausdauer; $("rAusdauer").textContent=Math.round(hud.ausdauer);
$("sSpeed").value=hud.tempo; $("rSpeed").textContent=Math.round(hud.tempo);
$("sFuel").value=hud.tank; $("rFuel").textContent=Math.round(hud.tank);
}
/* Aktions-Buttons */
$("bDamage").addEventListener("click",()=>{
// Schaden geht direkt auf das Leben
hud.leben=clamp(hud.leben-20,0,100);
syncSlider(); renderAll();
});
$("bHeal").addEventListener("click",()=>{hud.leben=100;syncSlider();renderAll();});
$("bEat").addEventListener("click",()=>{hud.hunger=clamp(hud.hunger+25,0,100);syncSlider();renderAll();});
$("bDrink").addEventListener("click",()=>{hud.durst=clamp(hud.durst+25,0,100);syncSlider();renderAll();});
/* „Sprechen" simulieren: Voice kurz aktiv schalten */
let redeTimer=null;
$("bTalk").addEventListener("click",()=>{
hud.redet=true; renderVoice(); clearTimeout(redeTimer);
redeTimer=setTimeout(()=>{hud.redet=false;renderVoice();},1500);
});
/* Voice-Reichweite durchschalten */
$("bVoice").addEventListener("click",()=>{hud.voiceStufe=(hud.voiceStufe+1)%voiceStufen.length;renderVoice();});
/* Geld-Buttons */
$("bCash").addEventListener("click",()=>{hud.bargeld+=500;renderMoney();blinkeGeld("cash");});
$("bBank").addEventListener("click",()=>{hud.konto=Math.max(0,hud.konto-250);renderMoney();blinkeGeld("bank");});
$("bDirty").addEventListener("click",()=>{hud.schwarzgeldSichtbar=!hud.schwarzgeldSichtbar;renderMoney();if(hud.schwarzgeldSichtbar)blinkeGeld("dirty");});
/* Fahrzeug-Buttons */
$("bVehicle").addEventListener("click",()=>{
hud.imFahrzeug=!hud.imFahrzeug;
$("bVehicle").textContent=hud.imFahrzeug?"🚶 Aussteigen":"🚗 Einsteigen";
$("bVehicle").classList.toggle("on",hud.imFahrzeug);
if(!hud.imFahrzeug){hud.tempo=0;syncSlider();} // beim Aussteigen Tempo zurücksetzen
renderAll();
});
$("bSeat").addEventListener("click",()=>{hud.gurt=!hud.gurt;$("bSeat").classList.toggle("on",hud.gurt);renderVehicle();});
$("bEngine").addEventListener("click",()=>{hud.motor=!hud.motor;$("bEngine").classList.toggle("on",hud.motor);$("bEngine").textContent=hud.motor?"⚙️ Motor an":"⚙️ Motor aus";renderVehicle();});
/* Demo-Panel ein-/ausklappen */
$("demoHead").addEventListener("click",()=>$("demo").classList.toggle("collapsed"));
/* =====================================================================
LEBENDIGE SIMULATION (langsamer Verbrauch)
===================================================================== */
/* Hunger/Durst sinken langsam; leerer Magen zieht am Leben; Tank sinkt bei Fahrt */
function tickVerbrauchsWerte(){
hud.hunger=clamp(hud.hunger-1,0,100);
hud.durst=clamp(hud.durst-1.4,0,100);
if(hud.hunger<=0||hud.durst<=0)hud.leben=clamp(hud.leben-2,0,100); // Verhungern/Verdursten
if(hud.imFahrzeug&&hud.motor&&hud.tempo>0)hud.tank=clamp(hud.tank-0.6,0,100);
if(hud.tank<=0)hud.tempo=0; // ohne Sprit kein Vortrieb
syncSlider(); renderAll();
}
/* =====================================================================
NUI-BRÜCKE — Werte kommen vom FiveM-Client
===================================================================== */
/* Läuft das HUD in FiveM oder eigenständig im Browser? */
const IST_FIVEM = (typeof GetParentResourceName === "function");
/* Übernimmt nur die Felder, die tatsächlich mitgeschickt wurden */
function uebernehme(ziel, quelle, felder){
for(const feld of felder){
if(quelle[feld] !== undefined && quelle[feld] !== null) ziel[feld] = quelle[feld];
}
}
window.addEventListener("message", (ereignis) => {
const n = ereignis.data || {};
if(n.aktion === "status"){ // Leben, Hunger, Durst, Ausdauer
uebernehme(hud, n, ["leben","hunger","durst","ausdauer"]);
renderGauges();
}
else if(n.aktion === "geld"){ // Bargeld, Konto, Schwarzgeld
const vorherBar = hud.bargeld, vorherKonto = hud.konto;
uebernehme(hud, n, ["bargeld","konto","schwarzgeld","schwarzgeldSichtbar"]);
renderMoney();
// Geänderte Beträge kurz hervorheben
if(n.bargeld !== undefined && n.bargeld !== vorherBar) blinkeGeld("cash");
if(n.konto !== undefined && n.konto !== vorherKonto) blinkeGeld("bank");
}
else if(n.aktion === "voice"){ // Sprechen und Reichweite
uebernehme(hud, n, ["redet","voiceStufe"]);
renderVoice();
}
else if(n.aktion === "fahrzeug"){ // Tacho, Tank, Gurt, Motor
uebernehme(hud, n, ["imFahrzeug","tempo","tank","gurt","motor"]);
renderVehicle();
}
else if(n.aktion === "sichtbar"){ // HUD ein-/ausblenden
document.body.classList.toggle("hud-aus", n.an === false);
}
});
/* =====================================================================
START
===================================================================== */
baueGauges();
if(IST_FIVEM){
// Im Spiel: Kulisse und Demo-Panel ausblenden, keine Simulation laufen lassen
document.body.classList.add("ingame");
// Startwerte neutral halten, bis der Client die echten Werte schickt
Object.assign(hud, {leben:100, hunger:100, durst:100, ausdauer:100,
bargeld:0, konto:0, schwarzgeld:0, imFahrzeug:false, tempo:0});
renderAll();
} else {
// Eigenständige Browser-Ansicht: Demo-Panel und Verbrauchssimulation aktiv
syncSlider();
renderAll();
setInterval(tickVerbrauchsWerte, 4000);
}
</script>
</body>
</html>