aether-framework/factions/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

382 lines
43 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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">
</head>
<body>
<title>Aether — Fraktionsverwaltung</title>
<style>
:root{
--bg:#080a0f;--fg:#e9ecf4;--muted:#828a9e;--surface:rgba(22,26,36,0.72);--surface2:rgba(34,39,52,0.85);
--slot:#111520;--border:rgba(255,255,255,0.09);--accent:#7c5cf6;--ink:#f4f0ff;--accent-soft:rgba(124,92,246,0.16);
--green:#34d39a;--amber:#f5b23d;--red:#f0506e;--blue:#4aa8ff;
}
*{box-sizing:border-box;} html,body{margin:0;height:100%;}
body{min-height:100%;background:radial-gradient(1100px 700px at 15% 0%,var(--accent-soft),transparent 55%),radial-gradient(900px 700px at 90% 100%,rgba(52,211,153,0.05),transparent 55%),var(--bg);
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:var(--fg);
display:flex;align-items:center;justify-content:center;padding:22px 14px;overflow-x:hidden;transition:background .4s;}
.app{position:relative;width:min(1280px,98vw);aspect-ratio:16/10;border-radius:20px;overflow:hidden;
background:linear-gradient(160deg,rgba(18,22,30,0.92),rgba(9,11,16,0.96));backdrop-filter:blur(20px);border:1px solid var(--border);
box-shadow:0 50px 100px -30px rgba(0,0,0,0.85);display:grid;grid-template-columns:222px 1fr;grid-template-rows:auto 1fr;grid-template-areas:"brand top" "nav main";}
.brand{grid-area:brand;padding:13px 16px;border-right:1px solid var(--border);border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;}
.brand .bl{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;font-weight:800;color:#fff;font-size:15px;}
.brand .bn{font-size:15px;font-weight:800;letter-spacing:-0.02em;line-height:1.1;} .brand .bn small{display:block;font-size:10px;color:var(--muted);font-weight:500;}
.top{grid-area:top;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:12px;padding:0 18px;}
.top select{background:var(--slot);border:1px solid var(--border);color:var(--fg);border-radius:9px;padding:8px 11px;font-family:inherit;font-size:12.5px;font-weight:600;}
.top .kasse{margin-left:auto;background:var(--surface);border:1px solid var(--border);border-radius:10px;padding:7px 13px;text-align:right;} .top .kasse .l{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;font-weight:800;} .top .kasse .v{font-size:15px;font-weight:800;font-variant-numeric:tabular-nums;color:var(--green);}
.top .me{display:flex;align-items:center;gap:9px;padding-left:14px;border-left:1px solid var(--border);} .top .me .av{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;font-weight:800;font-size:12px;color:#fff;} .top .me .mn{font-size:13px;font-weight:700;} .top .me .mr{font-size:10.5px;color:var(--accent);font-weight:700;}
.nav{grid-area:nav;border-right:1px solid var(--border);padding:11px 9px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;}
.nav button{display:flex;align-items:center;gap:10px;padding:9px 11px;border-radius:9px;border:none;background:none;color:var(--muted);font-family:inherit;font-size:13.5px;font-weight:700;cursor:pointer;text-align:left;transition:.12s;}
.nav button .ni{font-size:15px;width:18px;text-align:center;} .nav button:hover{background:var(--surface);color:var(--fg);} .nav button.active{background:var(--accent-soft);color:var(--accent);}
.nav button .badge{margin-left:auto;background:var(--slot);color:var(--muted);font-size:10px;font-weight:800;border-radius:999px;padding:1px 7px;}
.main{grid-area:main;overflow-y:auto;padding:18px 22px;} .main::-webkit-scrollbar,.nav::-webkit-scrollbar,.tblwrap::-webkit-scrollbar{width:7px;} .main::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.09);border-radius:4px;}
.phead{display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:14px;} .phead h2{font-size:20px;margin:0;font-weight:800;letter-spacing:-0.02em;} .phead p{margin:2px 0 0;color:var(--muted);font-size:12.5px;}
.grid{display:grid;gap:12px;} .card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:15px;}
.kpi .l{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;font-weight:800;} .kpi .v{font-size:24px;font-weight:800;font-variant-numeric:tabular-nums;margin-top:3px;} .kpi .d{font-size:11.5px;font-weight:600;margin-top:1px;}
.list-h{font-size:10.5px;text-transform:uppercase;letter-spacing:0.06em;color:var(--muted);font-weight:800;margin:0 0 10px;display:flex;align-items:center;gap:8px;} .list-h .r{margin-left:auto;font-weight:600;text-transform:none;letter-spacing:0;}
.btn{border:none;border-radius:10px;padding:10px 15px;font-size:13px;font-weight:700;cursor:pointer;font-family:inherit;transition:.12s;} .btn.primary{background:var(--accent);color:#fff;} .btn.primary:hover{filter:brightness(1.1);} .btn.ghost{background:var(--surface2);color:var(--fg);border:1px solid var(--border);} .btn.red{background:var(--red);color:#2a0910;} .btn.green{background:var(--green);color:#06231a;} .btn.sm{padding:6px 11px;font-size:12px;} .btn:disabled{opacity:.4;cursor:not-allowed;} .btn:active{transform:scale(0.98);}
.input,select.input{width:100%;background:var(--slot);border:1px solid var(--border);border-radius:9px;padding:10px 11px;color:var(--fg);font-size:13.5px;font-family:inherit;outline:none;} .input:focus{border-color:var(--accent);}
.flabel{font-size:10.5px;color:var(--muted);font-weight:800;text-transform:uppercase;letter-spacing:0.04em;margin:11px 2px 5px;display:block;}
.row{display:flex;align-items:center;gap:11px;padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:12px;margin-bottom:7px;} .row .grow{flex:1;min-width:0;} .row .t{font-weight:700;font-size:13.5px;} .row .m{font-size:11.5px;color:var(--muted);}
.av{border-radius:50%;display:grid;place-items:center;font-weight:800;color:#fff;flex-shrink:0;width:36px;height:36px;font-size:12px;}
.pill{font-size:10px;font-weight:800;padding:3px 8px;border-radius:999px;display:inline-block;} .pill.on{background:rgba(52,211,153,0.16);color:var(--green);} .pill.off{background:rgba(255,255,255,0.08);color:var(--muted);} .pill.a{background:rgba(245,178,61,0.16);color:var(--amber);} .pill.r{background:rgba(240,80,110,0.16);color:var(--red);} .pill.ac{background:var(--accent-soft);color:var(--accent);}
.tblwrap{overflow-x:auto;} table.tbl{width:100%;border-collapse:collapse;font-size:12.5px;} .tbl th{text-align:left;color:var(--muted);font-size:10px;text-transform:uppercase;letter-spacing:0.05em;padding:8px 9px;border-bottom:1px solid var(--border);white-space:nowrap;} .tbl td{padding:9px 9px;border-bottom:1px solid var(--border);} .tbl tr:last-child td{border:none;} .tbl .num{text-align:right;font-variant-numeric:tabular-nums;} .tbl .c{text-align:center;}
.toggle{width:34px;height:20px;border-radius:999px;background:var(--slot);position:relative;cursor:pointer;border:1px solid var(--border);display:inline-block;vertical-align:middle;transition:.15s;} .toggle::after{content:"";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;background:#fff;transition:.15s;} .toggle.on{background:var(--accent);border-color:transparent;} .toggle.on::after{transform:translateX(14px);} .toggle.dis{opacity:.4;cursor:not-allowed;}
.empty{text-align:center;color:var(--muted);padding:36px 10px;} .empty .big{font-size:36px;margin-bottom:8px;}
.bartrack{height:8px;border-radius:5px;background:var(--slot);overflow:hidden;} .bartrack>i{display:block;height:100%;border-radius:5px;}
/* Territorien-Karte */
.tmap{position:relative;border-radius:14px;overflow:hidden;border:1px solid var(--border);height:340px;
background:repeating-linear-gradient(90deg,rgba(255,255,255,0.02) 0 1px,transparent 1px 56px),repeating-linear-gradient(0deg,rgba(255,255,255,0.02) 0 1px,transparent 1px 56px),linear-gradient(135deg,#101823,#0a0f16);}
.zone{position:absolute;border-radius:10px;border:1.5px solid;cursor:pointer;display:flex;align-items:flex-end;padding:7px;transition:.12s;} .zone:hover{filter:brightness(1.15);}
.zone .zn{font-size:11px;font-weight:800;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.7);} .zone .zc{position:absolute;top:6px;right:8px;font-size:10px;font-weight:800;color:#fff;text-shadow:0 1px 2px #000;}
.overlay{position:absolute;inset:0;z-index:40;background:rgba(4,6,10,0.72);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;padding:22px;} .overlay.show{display:flex;}
.modal{width:460px;max-width:97%;max-height:90%;overflow-y:auto;background:rgba(18,22,30,0.98);border:1px solid var(--border);border-radius:16px;padding:20px;box-shadow:0 30px 70px -18px rgba(0,0,0,0.8);} .modal.sm{width:380px;}
.modal h3{margin:0 0 3px;font-size:18px;} .modal .msub{color:var(--muted);font-size:12.5px;margin-bottom:12px;} .modal-actions{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:14px;}
.kv{display:flex;justify-content:space-between;font-size:12.5px;padding:5px 0;border-bottom:1px solid var(--border);} .kv:last-child{border:none;} .kv span:first-child{color:var(--muted);}
.toast{position:absolute;bottom:16px;left:50%;transform:translateX(-50%) translateY(16px);background:rgba(12,15,22,0.97);border:1px solid var(--border);color:var(--fg);padding:10px 18px;border-radius:11px;font-size:13px;font-weight:600;z-index:50;opacity:0;pointer-events:none;transition:.3s;} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} .toast.good{border-color:var(--green);} .toast.warn{border-color:var(--red);}
</style>
<div class="app">
<div class="brand"><span class="bl" id="brand-logo">B</span><div class="bn" id="brand-name">Fraktion<small>Verwaltung</small></div></div>
<div class="top" id="top"></div>
<div class="nav" id="nav"></div>
<div class="main" id="main"></div>
<div class="overlay" id="overlay"></div>
<div class="toast" id="toast"></div>
</div>
<script>
"use strict";
const $=id=>document.getElementById(id);
function sicher(t){const d=document.createElement("div");d.textContent=t==null?"":t;return d.innerHTML;}
const euro=n=>Math.round(n).toLocaleString("de-DE")+" €";
const initialen=n=>n.split(" ").map(w=>w[0]).slice(0,2).join("");
const avColor=n=>{let h=0;for(const c of n)h=(h*31+c.charCodeAt(0))%360;return `hsl(${h} 45% 46%)`;};
let toastTimer=null; function toast(t,typ){const e=$("toast");e.textContent=t;e.className="toast show"+(typ?" "+typ:"");clearTimeout(toastTimer);toastTimer=setTimeout(()=>e.classList.remove("show"),2400);}
function modal(html,sm){$("overlay").innerHTML=`<div class="modal${sm?' sm':''}">${html}</div>`;$("overlay").classList.add("show");}
function closeModal(){$("overlay").classList.remove("show");$("overlay").innerHTML="";}
/* =====================================================================
RECHTE-DEFINITIONEN
===================================================================== */
const RECHTE=[["einladen","Einladen"],["kicken","Kicken"],["befoerdern","Befördern"],["kasse","Kasse"],["raenge","Ränge"],["territorien","Territorien"],["fahrzeuge","Fahrzeuge"],["einstellungen","Einstell."]];
const alleRechte=(v)=>Object.fromEntries(RECHTE.map(r=>[r[0],v]));
function rang(name,gehalt,rechte){return {name,gehalt,rechte:{...alleRechte(false),...rechte}};}
const spieler={bargeld:12500}; // eigenes Bargeld (für Kasse-Einzahlung)
/* =====================================================================
FRAKTIONEN
===================================================================== */
const fraktionen=[
{id:"ballas",name:"Ballas",kurz:"Straßengang · Grove/​Davis",typ:"gang",farbe:"#7c5cf6",ink:"#f4f0ff",kasse:184000,
raenge:[rang("Boss",0,alleRechte(true)),rang("Underboss",4000,{einladen:1,kicken:1,befoerdern:1,kasse:1,territorien:1,fahrzeuge:1}),rang("Enforcer",2600,{einladen:1,kicken:1,territorien:1,fahrzeuge:1}),rang("Soldier",1500,{fahrzeuge:1}),rang("Prospect",600,{})],
mitglieder:[{name:"Deebo King",rankIdx:0,seit:"12.01.2026",letzte:"online",online:true},{name:"Vince Malloy",rankIdx:1,seit:"02.02.2026",letzte:"online",online:true},{name:"Rico Vargas",rankIdx:2,seit:"14.03.2026",letzte:"online",online:true},{name:"Tyrone Banks",rankIdx:3,seit:"20.04.2026",letzte:"vor 2 h",online:false},{name:"Marcus Hale",rankIdx:3,seit:"01.05.2026",letzte:"online",online:true},{name:"Lil J",rankIdx:4,seit:"18.06.2026",letzte:"vor 1 Tag",online:false}],
territorien:[
{name:"Grove Street",x:14,y:60,w:24,h:26,kontrolle:88,einnahmen:320,rivale:"Families"},
{name:"Davis",x:42,y:64,w:22,h:24,kontrolle:64,einnahmen:210,rivale:"Vagos"},
{name:"Chamberlain Hills",x:20,y:26,w:26,h:24,kontrolle:41,einnahmen:150,rivale:"Vagos"},
{name:"Strawberry",x:56,y:30,w:24,h:22,kontrolle:12,einnahmen:0,rivale:"Vagos"}],
fahrzeuge:[{model:"Manana",plate:"BAL-01",zugewiesen:"Enforcer+"},{model:"Baller",plate:"BAL-02",zugewiesen:"Boss"}],
logs:[], transaktionen:[{typ:"in",text:"Territorium Grove Street",betrag:320,zeit:"heute"},{typ:"out",text:"Waffenkauf",betrag:-8000,zeit:"gestern"}]},
{id:"vagos",name:"Vagos",kurz:"Straßengang · East LS",typ:"gang",farbe:"#f5b23d",ink:"#3a2600",kasse:97000,
raenge:[rang("Boss",0,alleRechte(true)),rang("Underboss",3800,{einladen:1,kicken:1,befoerdern:1,kasse:1,territorien:1,fahrzeuge:1}),rang("Veterano",2400,{einladen:1,kicken:1,territorien:1}),rang("Soldado",1400,{fahrzeuge:1}),rang("Novato",500,{})],
mitglieder:[{name:"Miguel Cruz",rankIdx:0,seit:"08.01.2026",letzte:"online",online:true},{name:"Ana Reyes",rankIdx:1,seit:"11.02.2026",letzte:"vor 30 min",online:false},{name:"Diego Sol",rankIdx:2,seit:"22.03.2026",letzte:"online",online:true},{name:"Pablo Nieto",rankIdx:3,seit:"14.05.2026",letzte:"online",online:true}],
territorien:[
{name:"El Rancho",x:60,y:56,w:26,h:26,kontrolle:82,einnahmen:280,rivale:"Ballas"},
{name:"Cypress Flats",x:60,y:24,w:24,h:24,kontrolle:58,einnahmen:190,rivale:"—"},
{name:"Davis",x:36,y:62,w:22,h:22,kontrolle:36,einnahmen:110,rivale:"Ballas"}],
fahrzeuge:[{model:"Buccaneer",plate:"VAG-01",zugewiesen:"alle"}],
logs:[], transaktionen:[{typ:"in",text:"Schutzgeld East LS",betrag:1500,zeit:"heute"}]},
{id:"lsc",name:"Los Santos Customs",kurz:"Legaler Betrieb · Werkstatt",typ:"legal",farbe:"#34d39a",ink:"#06231a",kasse:342000,
raenge:[rang("Inhaber",0,alleRechte(true)),rang("Werkstattmeister",4200,{einladen:1,kicken:1,befoerdern:1,kasse:1,fahrzeuge:1}),rang("Geselle",2400,{fahrzeuge:1}),rang("Lehrling",1200,{})],
mitglieder:[{name:"Karl Mendez",rankIdx:0,seit:"03.11.2025",letzte:"online",online:true},{name:"Ben Fischer",rankIdx:1,seit:"20.12.2025",letzte:"online",online:true},{name:"Sara Lind",rankIdx:2,seit:"14.02.2026",letzte:"vor 5 h",online:false},{name:"Tom Kessler",rankIdx:3,seit:"30.04.2026",letzte:"online",online:true}],
territorien:[],
fahrzeuge:[{model:"Flatbed",plate:"LSC-01",zugewiesen:"Geselle+"},{model:"Towtruck",plate:"LSC-02",zugewiesen:"alle"},{model:"Sultan RS",plate:"LSC-03",zugewiesen:"Meister+"}],
logs:[], transaktionen:[{typ:"in",text:"Reparaturaufträge",betrag:12400,zeit:"heute"},{typ:"out",text:"Ersatzteile",betrag:-3200,zeit:"gestern"}]}
];
let aktiv=0; // aktive Fraktion
let ansichtRang=0; // "angemeldet als" Rang (zum Testen der Rechte); 0 = Boss/Inhaber
let sektion="uebersicht";
const F=()=>fraktionen[aktiv];
const R=()=>F().raenge[ansichtRang];
const darf=recht=>!!R().rechte[recht];
const rangName=idx=>F().raenge[idx].name;
function log(aktion,detail){F().logs.unshift({zeit:new Date(),wer:F().mitglieder.find(m=>m.rankIdx===ansichtRang)?.name||rangName(ansichtRang),aktion,detail:detail||""});}
/* =====================================================================
SHELL
===================================================================== */
const nav=[
{id:"uebersicht",label:"Übersicht",icon:"🏠"},
{id:"mitglieder",label:"Mitglieder",icon:"👥"},
{id:"raenge",label:"Ränge & Rechte",icon:"🎖️"},
{id:"kasse",label:"Kasse",icon:"💰"},
{id:"territorien",label:"Territorien",icon:"🗺️",gang:true},
{id:"fahrzeuge",label:"Fahrzeuge",icon:"🚗"},
{id:"logbuch",label:"Logbuch",icon:"📜"},
{id:"einstellungen",label:"Einstellungen",icon:"⚙️"}
];
function starte(){
document.documentElement.style.setProperty("--accent",F().farbe);
document.documentElement.style.setProperty("--ink",F().ink);
document.documentElement.style.setProperty("--accent-soft","color-mix(in srgb, "+F().farbe+" 16%, transparent)");
$("brand-logo").style.background=F().farbe; $("brand-logo").textContent=initialen(F().name);
$("brand-name").innerHTML=`${sicher(F().name)}<small>${F().typ==="gang"?"Gang-Verwaltung":"Fraktions-Verwaltung"}</small>`;
renderTop(); renderNav(); renderSektion();
}
function renderTop(){
$("top").innerHTML=`<select id="frak-sel">${fraktionen.map((f,i)=>`<option value="${i}" ${i===aktiv?'selected':''}>${sicher(f.name)}</option>`).join('')}</select>
<select id="rang-sel" title="Ansicht als Rang (Rechte testen)">${F().raenge.map((r,i)=>`<option value="${i}" ${i===ansichtRang?'selected':''}>Als: ${sicher(r.name)}</option>`).join('')}</select>
<div class="kasse"><div class="l">Fraktionskasse</div><div class="v">${euro(F().kasse)}</div></div>
<div class="me"><span class="av" style="background:${F().farbe}">${initialen(rangName(ansichtRang))}</span><div><div class="mn">${sicher(rangName(ansichtRang))}</div><div class="mr">${F().mitglieder.filter(m=>m.online).length} online</div></div></div>`;
$("frak-sel").addEventListener("change",e=>{aktiv=+e.target.value;ansichtRang=0;sektion="uebersicht";starte();});
$("rang-sel").addEventListener("change",e=>{ansichtRang=+e.target.value;starte();});
}
function renderNav(){
$("nav").innerHTML=nav.filter(n=>!n.gang||F().typ==="gang").map(n=>`<button data-s="${n.id}" class="${n.id===sektion?'active':''}"><span class="ni">${n.icon}</span>${n.label}${n.id==='mitglieder'?`<span class="badge">${F().mitglieder.length}</span>`:''}</button>`).join('');
$("nav").querySelectorAll("[data-s]").forEach(b=>b.addEventListener("click",()=>{sektion=b.dataset.s;renderNav();renderSektion();}));
}
function renderSektion(){(sektionen[sektion]||sektionen.uebersicht)();}
const sektionen={};
/* =====================================================================
ÜBERSICHT
===================================================================== */
sektionen.uebersicht=()=>{
const online=F().mitglieder.filter(m=>m.online).length, payday=F().mitglieder.reduce((s,m)=>s+F().raenge[m.rankIdx].gehalt,0);
const terrIncome=F().territorien.reduce((s,t)=>s+(t.kontrolle>=50?t.einnahmen:0),0);
$("main").innerHTML=`<div class="phead"><div><h2>${sicher(F().name)}</h2><p>${sicher(F().kurz)}</p></div></div>
<div class="grid" style="grid-template-columns:repeat(4,1fr)">
<div class="card kpi"><div class="l">👥 Mitglieder</div><div class="v">${F().mitglieder.length}</div><div class="d" style="color:var(--green)">${online} online</div></div>
<div class="card kpi"><div class="l">💰 Kasse</div><div class="v">${euro(F().kasse)}</div><div class="d" style="color:var(--muted)">Fraktionsvermögen</div></div>
<div class="card kpi"><div class="l">🧾 Payday /h</div><div class="v">${euro(payday)}</div><div class="d" style="color:var(--muted)">Gehälter gesamt</div></div>
<div class="card kpi"><div class="l">${F().typ==='gang'?'🗺️ Gebiets-Einnahmen':'🔧 Aufträge /h'}</div><div class="v" style="color:var(--green)">${F().typ==='gang'?euro(terrIncome)+' /h':euro(8400)+' /h'}</div><div class="d" style="color:var(--muted)">${F().typ==='gang'?F().territorien.filter(t=>t.kontrolle>=50).length+' kontrollierte Gebiete':'geschätzt'}</div></div></div>
<div class="grid" style="grid-template-columns:1fr 1fr;margin-top:12px">
<div class="card"><div class="list-h">Online-Mitglieder</div>${F().mitglieder.filter(m=>m.online).map(m=>`<div class="row" style="margin:0 0 7px"><span class="av" style="background:${avColor(m.name)}">${initialen(m.name)}</span><div class="grow"><div class="t">${sicher(m.name)}</div><div class="m">${rangName(m.rankIdx)}</div></div><span class="pill on">online</span></div>`).join('')||'<div class="empty">Niemand online.</div>'}</div>
<div class="card"><div class="list-h">Schnellaktionen</div><div style="display:flex;flex-direction:column;gap:8px">
<button class="btn ghost" id="q-invite" ${darf('einladen')?'':'disabled'}>➕ Mitglied einladen</button>
<button class="btn ghost" id="q-payday" ${darf('kasse')?'':'disabled'}>🧾 Payday auszahlen (${euro(payday)})</button>
<button class="btn ghost" id="q-deposit" ${darf('kasse')?'':'disabled'}>💵 In Kasse einzahlen</button>
${F().typ==='gang'?'<button class="btn ghost" id="q-terr">🗺️ Territorien verwalten</button>':''}</div>
${darf('einladen')?'':'<div style="font-size:11px;color:var(--muted);margin-top:8px">🔒 Als '+sicher(rangName(ansichtRang))+' hast du eingeschränkte Rechte.</div>'}</div></div>`;
const qi=$("q-invite");if(qi)qi.addEventListener("click",()=>darf('einladen')&&einladenModal());
const qp=$("q-payday");if(qp)qp.addEventListener("click",()=>darf('kasse')&&payday());
const qd=$("q-deposit");if(qd)qd.addEventListener("click",()=>darf('kasse')&&kasseModal('in'));
const qt=$("q-terr");if(qt)qt.addEventListener("click",()=>{sektion="territorien";renderNav();renderSektion();});
};
/* =====================================================================
MITGLIEDER
===================================================================== */
sektionen.mitglieder=()=>{
$("main").innerHTML=`<div class="phead"><div><h2>Mitglieder</h2><p>${F().mitglieder.length} Mitglieder · ${F().mitglieder.filter(m=>m.online).length} online</p></div>
<button class="btn primary" id="m-invite" ${darf('einladen')?'':'disabled'}>➕ Einladen</button></div>
<div class="card" style="padding:6px 12px"><div class="tblwrap"><table class="tbl"><thead><tr><th>Mitglied</th><th>Rang</th><th class="num">Gehalt</th><th>Seit</th><th>Status</th><th></th></tr></thead><tbody>
${F().mitglieder.map((m,i)=>`<tr><td><span class="av" style="width:26px;height:26px;font-size:10px;background:${avColor(m.name)}">${initialen(m.name)}</span> ${sicher(m.name)}</td>
<td><span class="pill ac">${rangName(m.rankIdx)}</span></td><td class="num">${euro(F().raenge[m.rankIdx].gehalt)}</td><td style="color:var(--muted)">${m.seit}</td>
<td>${m.online?'<span class="pill on">online</span>':`<span class="pill off">${sicher(m.letzte)}</span>`}</td>
<td class="num"><button class="btn ghost sm" data-up="${i}" ${kannBefoerdern(m,1)?'':'disabled'}>▲</button> <button class="btn ghost sm" data-down="${i}" ${kannBefoerdern(m,-1)?'':'disabled'}>▼</button> <button class="btn red sm" data-kick="${i}" ${kannKicken(m)?'':'disabled'}>Kick</button></td></tr>`).join('')}
</tbody></table></div></div>
${darf('befoerdern')||darf('kicken')?'':'<div class="card" style="margin-top:10px;color:var(--muted);font-size:12px">🔒 Als '+sicher(rangName(ansichtRang))+' kannst du keine Ränge ändern oder Mitglieder entfernen.</div>'}`;
const mi=$("m-invite");if(mi)mi.addEventListener("click",()=>darf('einladen')&&einladenModal());
$("main").querySelectorAll("[data-up]").forEach(b=>b.addEventListener("click",()=>befoerdern(+b.dataset.up,1)));
$("main").querySelectorAll("[data-down]").forEach(b=>b.addEventListener("click",()=>befoerdern(+b.dataset.down,-1)));
$("main").querySelectorAll("[data-kick]").forEach(b=>b.addEventListener("click",()=>kickMitglied(+b.dataset.kick)));
};
function kannBefoerdern(m,dir){ if(!darf('befoerdern'))return false; const neu=m.rankIdx-dir; // dir +1 = hoch (kleinerer Index)
if(neu<0||neu>=F().raenge.length)return false; if(neu<=ansichtRang&&ansichtRang!==0)return false; // nicht auf/über eigenen Rang befördern (außer Boss)
if(m.rankIdx<=ansichtRang&&ansichtRang!==0)return false; return true; }
function kannKicken(m){ if(!darf('kicken'))return false; return m.rankIdx>ansichtRang||ansichtRang===0&&m.rankIdx!==0; }
function befoerdern(i,dir){const m=F().mitglieder[i];if(!kannBefoerdern(m,dir))return;m.rankIdx-=dir;log(dir>0?"Befördert":"Degradiert",m.name+" → "+rangName(m.rankIdx));toast(m.name+" "+(dir>0?"befördert":"degradiert")+" zu "+rangName(m.rankIdx),"good");renderSektion();renderTop();}
function kickMitglied(i){const m=F().mitglieder[i];if(!kannKicken(m))return;
modal(`<h3>Mitglied entfernen?</h3><p style="color:var(--muted);font-size:13px">„${sicher(m.name)}“ (${rangName(m.rankIdx)}) aus ${sicher(F().name)} entfernen?</p><div class="modal-actions"><button class="btn ghost" id="k-c">Abbrechen</button><button class="btn red" id="k-ok">Entfernen</button></div>`,true);
$("k-c").addEventListener("click",closeModal);$("k-ok").addEventListener("click",()=>{F().mitglieder.splice(i,1);log("Mitglied entfernt",m.name);closeModal();toast(m.name+" entfernt","warn");renderSektion();renderTop();renderNav();});}
function einladenModal(){
modal(`<h3>Mitglied einladen</h3><div class="msub">Neues Mitglied zu ${sicher(F().name)} hinzufügen</div>
<label class="flabel">Name / ID</label><input class="input" id="ei-name" placeholder="z. B. Max Weber">
<label class="flabel">Startrang</label><select class="input" id="ei-rang">${F().raenge.map((r,i)=>i>0?`<option value="${i}" ${i===F().raenge.length-1?'selected':''}>${sicher(r.name)}</option>`:'').join('')}</select>
<div class="modal-actions"><button class="btn ghost" id="ei-c">Abbrechen</button><button class="btn primary" id="ei-ok">Einladen</button></div>`,true);
$("ei-c").addEventListener("click",closeModal);
$("ei-ok").addEventListener("click",()=>{const n=$("ei-name").value.trim();if(!n){toast("Name angeben","warn");return;}F().mitglieder.push({name:n,rankIdx:+$("ei-rang").value,seit:new Date().toLocaleDateString('de-DE'),letzte:"gerade beigetreten",online:true});log("Mitglied eingeladen",n);closeModal();toast(n+" eingeladen","good");renderSektion();renderTop();renderNav();});
}
/* =====================================================================
RÄNGE & RECHTE
===================================================================== */
sektionen.raenge=()=>{
const editier=darf('raenge');
$("main").innerHTML=`<div class="phead"><div><h2>Ränge &amp; Rechte</h2><p>${F().raenge.length} Ränge · Gehälter &amp; Berechtigungen</p></div>${editier?'<button class="btn primary" id="r-neu">➕ Rang</button>':''}</div>
<div class="card" style="padding:6px 12px"><div class="tblwrap"><table class="tbl"><thead><tr><th>Rang</th><th class="num">Gehalt /h</th>${RECHTE.map(r=>`<th class="c">${r[1]}</th>`).join('')}<th></th></tr></thead><tbody>
${F().raenge.map((r,i)=>`<tr><td><span class="pill ac">${sicher(r.name)}</span>${i===0?' <span class="pill a">Leitung</span>':''}</td>
<td class="num">${i===0?'—':euro(r.gehalt)}</td>
${RECHTE.map(rc=>`<td class="c"><span class="toggle ${r.rechte[rc[0]]?'on':''} ${editier&&i!==0?'':'dis'}" data-rt="${i}|${rc[0]}"></span></td>`).join('')}
<td class="num">${editier&&i!==0?`<button class="btn ghost sm" data-gehalt="${i}">Gehalt</button> <button class="btn red sm" data-delr="${i}" ${F().mitglieder.some(m=>m.rankIdx===i)?'disabled title="Rang belegt"':''}>✕</button>`:''}</td></tr>`).join('')}
</tbody></table></div></div>
<div class="card" style="margin-top:10px;font-size:12px;color:var(--muted)">Der oberste Rang (Leitung) besitzt automatisch alle Rechte. ${editier?'Klicke die Schalter, um Rechte je Rang zu ändern.':'🔒 Nur mit dem Recht „Ränge" bearbeitbar.'}</div>`;
if(editier){
const rn=$("r-neu");if(rn)rn.addEventListener("click",neuerRangModal);
$("main").querySelectorAll("[data-rt]").forEach(t=>t.addEventListener("click",()=>{const [i,rc]=t.dataset.rt.split('|');if(+i===0)return;const r=F().raenge[+i];r.rechte[rc]=!r.rechte[rc];log("Recht geändert",F().raenge[+i].name+": "+rc+" "+(r.rechte[rc]?'an':'aus'));renderSektion();}));
$("main").querySelectorAll("[data-gehalt]").forEach(b=>b.addEventListener("click",()=>{const r=F().raenge[+b.dataset.gehalt];
modal(`<h3>Gehalt · ${sicher(r.name)}</h3><label class="flabel">Gehalt (€ / Stunde Payday)</label><input class="input" id="rg" type="number" value="${r.gehalt}"><div class="modal-actions"><button class="btn ghost" id="rg-c">Abbrechen</button><button class="btn primary" id="rg-ok">Speichern</button></div>`,true);
$("rg-c").addEventListener("click",closeModal);$("rg-ok").addEventListener("click",()=>{r.gehalt=Math.max(0,parseInt($("rg").value,10)||0);log("Gehalt geändert",r.name+" → "+euro(r.gehalt));closeModal();toast("Gehalt aktualisiert","good");renderSektion();});}));
$("main").querySelectorAll("[data-delr]").forEach(b=>b.addEventListener("click",()=>{const i=+b.dataset.delr;if(F().mitglieder.some(m=>m.rankIdx===i)){toast("Rang ist belegt","warn");return;}const r=F().raenge.splice(i,1)[0];F().mitglieder.forEach(m=>{if(m.rankIdx>i)m.rankIdx--;});log("Rang gelöscht",r.name);toast("Rang gelöscht","warn");renderSektion();}));
}
};
function neuerRangModal(){
modal(`<h3>Neuer Rang</h3><label class="flabel">Name</label><input class="input" id="nr-name" placeholder="z. B. Veteran"><label class="flabel">Gehalt (€ / h)</label><input class="input" id="nr-gehalt" type="number" value="1000">
<div class="modal-actions"><button class="btn ghost" id="nr-c">Abbrechen</button><button class="btn primary" id="nr-ok">Erstellen</button></div>`,true);
$("nr-c").addEventListener("click",closeModal);
$("nr-ok").addEventListener("click",()=>{const n=$("nr-name").value.trim();if(!n){toast("Name angeben","warn");return;}F().raenge.push(rang(n,parseInt($("nr-gehalt").value,10)||0,{}));log("Rang erstellt",n);closeModal();toast("Rang „"+n+"“ erstellt","good");renderSektion();});
}
/* =====================================================================
KASSE
===================================================================== */
sektionen.kasse=()=>{
const kann=darf('kasse'), payday=F().mitglieder.reduce((s,m)=>s+F().raenge[m.rankIdx].gehalt,0);
$("main").innerHTML=`<div class="phead"><div><h2>Fraktionskasse</h2><p>Ein- und Auszahlungen · Payday</p></div></div>
<div class="grid" style="grid-template-columns:repeat(3,1fr);margin-bottom:12px">
<div class="card kpi"><div class="l">Kassenstand</div><div class="v" style="color:var(--green)">${euro(F().kasse)}</div></div>
<div class="card kpi"><div class="l">Payday /h</div><div class="v">${euro(payday)}</div><div class="d" style="color:var(--muted)">alle Gehälter</div></div>
<div class="card kpi"><div class="l">Dein Bargeld</div><div class="v">${euro(spieler.bargeld)}</div></div></div>
<div class="grid" style="grid-template-columns:1fr 1.4fr;align-items:start">
<div class="card"><div class="list-h">Aktionen</div><div style="display:flex;flex-direction:column;gap:8px">
<button class="btn ghost" id="k-in" ${kann?'':'disabled'}>💵 Einzahlen</button>
<button class="btn ghost" id="k-out" ${kann?'':'disabled'}>🏧 Auszahlen</button>
<button class="btn primary" id="k-pay" ${kann?'':'disabled'}>🧾 Payday auszahlen (${euro(payday)})</button></div>
${kann?'':'<div style="font-size:11px;color:var(--muted);margin-top:8px">🔒 Kein Recht „Kasse".</div>'}</div>
<div class="card"><div class="list-h">Transaktionen</div>${F().transaktionen.map(t=>`<div class="kv"><span>${t.typ==='in'?'⬆️':'⬇️'} ${sicher(t.text)}<span style="color:var(--muted)"> · ${t.zeit}</span></span><span style="color:${t.betrag>0?'var(--green)':'var(--red)'};font-weight:700">${t.betrag>0?'+':''}${euro(t.betrag)}</span></div>`).join('')||'<div class="empty">Keine Transaktionen.</div>'}</div></div>`;
const ki=$("k-in");if(ki)ki.addEventListener("click",()=>kann&&kasseModal('in'));
const ko=$("k-out");if(ko)ko.addEventListener("click",()=>kann&&kasseModal('out'));
const kp=$("k-pay");if(kp)kp.addEventListener("click",()=>kann&&payday());
};
function kasseModal(dir){
const rein=dir==='in';
modal(`<h3>${rein?'In Kasse einzahlen':'Aus Kasse auszahlen'}</h3><div class="msub">${rein?'Von deinem Bargeld ('+euro(spieler.bargeld)+')':'Auf dein Bargeld · Kasse: '+euro(F().kasse)}</div>
<label class="flabel">Betrag (€)</label><input class="input" id="ka-betrag" type="number" placeholder="0">
<label class="flabel">Verwendungszweck</label><input class="input" id="ka-zweck" placeholder="optional">
<div class="modal-actions"><button class="btn ghost" id="ka-c">Abbrechen</button><button class="btn primary" id="ka-ok">${rein?'Einzahlen':'Auszahlen'}</button></div>`,true);
$("ka-c").addEventListener("click",closeModal);
$("ka-ok").addEventListener("click",()=>{const b=parseInt($("ka-betrag").value,10);if(!b||b<=0){toast("Betrag angeben","warn");return;}
if(rein){if(b>spieler.bargeld){toast("Nicht genug Bargeld","warn");return;}spieler.bargeld-=b;F().kasse+=b;F().transaktionen.unshift({typ:'in',text:$("ka-zweck").value.trim()||"Einzahlung ("+rangName(ansichtRang)+")",betrag:b,zeit:"gerade eben"});log("Kasse eingezahlt",euro(b));toast(euro(b)+" eingezahlt","good");}
else{if(b>F().kasse){toast("Kasse nicht gedeckt","warn");return;}F().kasse-=b;spieler.bargeld+=b;F().transaktionen.unshift({typ:'out',text:$("ka-zweck").value.trim()||"Auszahlung ("+rangName(ansichtRang)+")",betrag:-b,zeit:"gerade eben"});log("Kasse ausgezahlt",euro(b));toast(euro(b)+" ausgezahlt","good");}
closeModal();renderSektion();renderTop();});
}
function payday(){const gesamt=F().mitglieder.reduce((s,m)=>s+F().raenge[m.rankIdx].gehalt,0);
if(gesamt>F().kasse){toast("Kasse deckt den Payday nicht ("+euro(gesamt)+")","warn");return;}
F().kasse-=gesamt;F().transaktionen.unshift({typ:'out',text:"Payday ("+F().mitglieder.length+" Mitglieder)",betrag:-gesamt,zeit:"gerade eben"});log("Payday ausgezahlt",euro(gesamt));toast("Payday "+euro(gesamt)+" ausgezahlt","good");renderSektion();renderTop();}
/* =====================================================================
TERRITORIEN (nur Gangs)
===================================================================== */
function terrStatus(k){return k>=66?["kontrolliert","on",F().farbe]:k>=33?["umkämpft","a","#f5b23d"]:["feindlich","r","#f0506e"];}
sektionen.territorien=()=>{
if(F().typ!=="gang"){$("main").innerHTML='<div class="empty" style="padding:60px"><div class="big">🏢</div>Territorien gibt es nur für Gangs.</div>';return;}
const kontrolliert=F().territorien.filter(t=>t.kontrolle>=50).length, income=F().territorien.reduce((s,t)=>s+(t.kontrolle>=50?t.einnahmen:0),0);
$("main").innerHTML=`<div class="phead"><div><h2>Territorien</h2><p>${kontrolliert}/${F().territorien.length} kontrolliert · ${euro(income)} /h Einnahmen</p></div></div>
<div style="display:grid;grid-template-columns:1fr 300px;gap:12px;align-items:start">
<div class="tmap" id="tmap">${F().territorien.map((t,i)=>{const st=terrStatus(t.kontrolle);return `<div class="zone" data-z="${i}" style="left:${t.x}%;top:${t.y}%;width:${t.w}%;height:${t.h}%;background:${st[2]}22;border-color:${st[2]}"><span class="zc">${t.kontrolle}%</span><span class="zn">${sicher(t.name)}</span></div>`;}).join('')}</div>
<div><div class="list-h">Gebiete</div>${F().territorien.map((t,i)=>{const st=terrStatus(t.kontrolle);return `<div class="card" style="padding:12px;margin-bottom:8px;cursor:pointer" data-z="${i}"><div style="display:flex;align-items:center;justify-content:space-between"><b>${sicher(t.name)}</b><span class="pill ${st[1]}">${st[0]}</span></div>
<div class="bartrack" style="margin:8px 0 5px"><i style="width:${t.kontrolle}%;background:${st[2]}"></i></div>
<div style="font-size:11.5px;color:var(--muted)">Kontrolle ${t.kontrolle}% · ${t.kontrolle>=50?euro(t.einnahmen)+' /h':'keine Einnahmen'} · Rivale: ${sicher(t.rivale)}</div></div>`;}).join('')}</div></div>`;
$("main").querySelectorAll("[data-z]").forEach(el=>el.addEventListener("click",()=>terrModal(+el.dataset.z)));
};
function terrModal(i){const t=F().territorien[i],st=terrStatus(t.kontrolle),kann=darf('territorien');
modal(`<h3>${sicher(t.name)}</h3><div class="msub">Rivale: ${sicher(t.rivale)}</div>
<div class="kv"><span>Kontrolle</span><span><span class="pill ${st[1]}">${st[0]}</span> ${t.kontrolle}%</span></div>
<div class="kv"><span>Einnahmen</span><span>${t.kontrolle>=50?euro(t.einnahmen)+' /h':'—'}</span></div>
<div class="bartrack" style="margin:10px 0"><i style="width:${t.kontrolle}%;background:${st[2]}"></i></div>
${kann?`<div class="modal-actions" style="grid-template-columns:1fr 1fr"><button class="btn primary" id="t-attack">⚔️ Angreifen</button><button class="btn ghost" id="t-defend">🛡️ Verstärken</button></div>
<div class="modal-actions" style="grid-template-columns:1fr 1fr;margin-top:8px"><button class="btn green" id="t-upgrade" ${t.kontrolle>=50?'':'disabled'}>⬆️ Ausbauen · ${euro(6000)}</button><button class="btn ghost" id="t-close">Schließen</button></div>`
:`<div style="font-size:12px;color:var(--muted);margin-top:8px">🔒 Kein Recht „Territorien".</div><div class="modal-actions" style="grid-template-columns:1fr;margin-top:8px"><button class="btn ghost" id="t-close">Schließen</button></div>`}`);
$("t-close").addEventListener("click",closeModal);
if(kann){
$("t-attack").addEventListener("click",()=>{t.kontrolle=Math.min(100,t.kontrolle+10+Math.floor(Math.random()*15));log("Angriff",t.name+" → "+t.kontrolle+"%");toast("Angriff auf "+t.name+" · Kontrolle "+t.kontrolle+"%","good");renderSektion();closeModal();});
$("t-defend").addEventListener("click",()=>{t.kontrolle=Math.min(100,t.kontrolle+5);log("Verstärkt",t.name);toast(t.name+" verstärkt · "+t.kontrolle+"%");renderSektion();closeModal();});
const up=$("t-upgrade");if(up)up.addEventListener("click",()=>{if(F().kasse<6000){toast("Kasse nicht gedeckt","warn");return;}F().kasse-=6000;t.einnahmen+=80;F().transaktionen.unshift({typ:'out',text:"Ausbau "+t.name,betrag:-6000,zeit:"gerade eben"});log("Gebiet ausgebaut",t.name+" +80/h");toast(t.name+" ausgebaut · +80 €/h","good");renderSektion();renderTop();closeModal();});
}
}
/* =====================================================================
FAHRZEUGE
===================================================================== */
sektionen.fahrzeuge=()=>{
const kann=darf('fahrzeuge');
$("main").innerHTML=`<div class="phead"><div><h2>Fraktionsfahrzeuge</h2><p>${F().fahrzeuge.length} Fahrzeuge · gemeinsame Garage</p></div>${kann?'<button class="btn primary" id="fz-add">➕ Hinzufügen</button>':''}</div>
<div class="card" style="padding:6px 12px"><div class="tblwrap"><table class="tbl"><thead><tr><th>Modell</th><th>Kennzeichen</th><th>Freigabe ab</th><th></th></tr></thead><tbody>
${F().fahrzeuge.map((v,i)=>`<tr><td>🚗 ${sicher(v.model)}</td><td class="mono">${sicher(v.plate)}</td><td>${sicher(v.zugewiesen)}</td><td class="num">${kann?`<button class="btn red sm" data-delv="${i}">Entfernen</button>`:''}</td></tr>`).join('')||'<tr><td colspan="4" class="empty">Keine Fahrzeuge.</td></tr>'}
</tbody></table></div></div>`;
const fa=$("fz-add");if(fa)fa.addEventListener("click",()=>{
modal(`<h3>Fahrzeug hinzufügen</h3><label class="flabel">Modell</label><input class="input" id="fv-model" placeholder="z. B. Sultan"><label class="flabel">Kennzeichen</label><input class="input" id="fv-plate" placeholder="z. B. GNG-04"><label class="flabel">Freigabe ab Rang</label><select class="input" id="fv-rang"><option value="alle">alle Mitglieder</option>${F().raenge.map(r=>`<option>${sicher(r.name)}+</option>`).join('')}</select><div class="modal-actions"><button class="btn ghost" id="fv-c">Abbrechen</button><button class="btn primary" id="fv-ok">Hinzufügen</button></div>`,true);
$("fv-c").addEventListener("click",closeModal);$("fv-ok").addEventListener("click",()=>{const m=$("fv-model").value.trim();if(!m){toast("Modell angeben","warn");return;}F().fahrzeuge.push({model:m,plate:$("fv-plate").value.trim()||"GNG-"+Math.floor(10+Math.random()*90),zugewiesen:$("fv-rang").value});log("Fahrzeug hinzugefügt",m);closeModal();toast(m+" hinzugefügt","good");renderSektion();});});
$("main").querySelectorAll("[data-delv]").forEach(b=>b.addEventListener("click",()=>{const v=F().fahrzeuge.splice(+b.dataset.delv,1)[0];log("Fahrzeug entfernt",v.plate);toast(v.model+" entfernt","warn");renderSektion();}));
};
/* =====================================================================
LOGBUCH
===================================================================== */
sektionen.logbuch=()=>{
$("main").innerHTML=`<div class="phead"><div><h2>Logbuch</h2><p>${F().logs.length} protokollierte Aktionen</p></div></div>
<div class="card" style="padding:6px 12px"><div class="tblwrap"><table class="tbl"><thead><tr><th>Zeit</th><th>Von</th><th>Aktion</th><th>Detail</th></tr></thead><tbody>
${F().logs.map(l=>`<tr><td class="mono" style="color:var(--muted)">${l.zeit.toLocaleTimeString('de-DE')}</td><td>${sicher(l.wer)}</td><td>${sicher(l.aktion)}</td><td style="color:var(--muted)">${sicher(l.detail)}</td></tr>`).join('')||'<tr><td colspan="4" class="empty">Noch keine Einträge.</td></tr>'}
</tbody></table></div></div>`;
};
/* =====================================================================
EINSTELLUNGEN
===================================================================== */
sektionen.einstellungen=()=>{
const kann=darf('einstellungen');
$("main").innerHTML=`<div class="phead"><div><h2>Einstellungen</h2><p>Fraktionsdaten &amp; Rekrutierung</p></div></div>
<div class="grid" style="grid-template-columns:1fr 1fr;align-items:start">
<div class="card"><div class="list-h">Fraktion</div>
<label class="flabel">Name</label><input class="input" id="s-name" value="${sicher(F().name)}" ${kann?'':'disabled'}>
<label class="flabel">Beschreibung</label><input class="input" id="s-desc" value="${sicher(F().kurz)}" ${kann?'':'disabled'}>
<label class="flabel">Farbe</label><div style="display:flex;gap:8px;flex-wrap:wrap" id="s-farbe">${["#7c5cf6","#f5b23d","#34d39a","#4aa8ff","#f0506e","#e0503f","#22c1d4","#c96fd6"].map(c=>`<div data-c="${c}" style="width:30px;height:30px;border-radius:8px;background:${c};cursor:${kann?'pointer':'default'};border:2px solid ${F().farbe===c?'#fff':'transparent'}"></div>`).join('')}</div>
${kann?'<button class="btn primary" id="s-save" style="margin-top:12px">Speichern</button>':'<div style="font-size:11px;color:var(--muted);margin-top:10px">🔒 Kein Recht „Einstellungen".</div>'}</div>
<div class="card"><div class="list-h">Typ &amp; Status</div>
<div class="kv"><span>Typ</span><span>${F().typ==='gang'?'Gang (illegal)':'Legale Fraktion'}</span></div>
<div class="kv"><span>Mitglieder</span><span>${F().mitglieder.length}</span></div>
<div class="kv"><span>Ränge</span><span>${F().raenge.length}</span></div>
<div class="kv"><span>Kasse</span><span>${euro(F().kasse)}</span></div>
${F().typ==='gang'?`<div class="kv"><span>Territorien</span><span>${F().territorien.length}</span></div>`:''}</div></div>`;
if(kann){
let farbe=F().farbe;
$("s-farbe").querySelectorAll("[data-c]").forEach(el=>el.addEventListener("click",()=>{farbe=el.dataset.c;$("s-farbe").querySelectorAll("[data-c]").forEach(x=>x.style.borderColor='transparent');el.style.borderColor='#fff';}));
$("s-save").addEventListener("click",()=>{F().name=$("s-name").value.trim()||F().name;F().kurz=$("s-desc").value.trim();F().farbe=farbe;log("Einstellungen geändert",F().name);toast("Gespeichert","good");starte();});
}
};
/* START */
starte();
</script>
</body>
</html>