- 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>
228 lines
21 KiB
HTML
228 lines
21 KiB
HTML
<!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 Threads — Bekleidung</title>
|
|
<style>
|
|
:root{
|
|
--bg:#08090d;--fg:#eef0f6;--muted:#868ca0;--surface:rgba(22,24,33,0.72);--surface2:rgba(36,39,52,0.85);
|
|
--slot:#12141b;--border:rgba(255,255,255,0.1);--accent:#c96fd6;--ink:#2a0a30;--accent-soft:rgba(201,111,214,0.16);
|
|
--red:#f0506e;--green:#34d39a;--amber:#f5b23d;
|
|
}
|
|
*{box-sizing:border-box;} html,body{margin:0;height:100%;}
|
|
body{min-height:100%;background:radial-gradient(1100px 700px at 20% 0%,rgba(201,111,214,0.1),transparent 55%),radial-gradient(900px 700px at 90% 100%,rgba(74,168,255,0.06),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:24px 16px;overflow-x:hidden;}
|
|
.app{position:relative;width:min(1200px,97vw);aspect-ratio:16/10;border-radius:22px;overflow:hidden;
|
|
background:radial-gradient(120% 90% at 50% 0%,#17141f,#0a0a0f 70%);border:1px solid var(--border);box-shadow:0 50px 100px -30px rgba(0,0,0,0.85);display:grid;grid-template-columns:1fr 400px;}
|
|
/* Vorschau */
|
|
.preview{position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;background:radial-gradient(60% 55% at 50% 46%,rgba(201,111,214,0.09),transparent 70%);}
|
|
.preview .floor{position:absolute;bottom:9%;left:50%;transform:translateX(-50%);width:44%;height:4%;border-radius:50%;background:radial-gradient(ellipse,rgba(201,111,214,0.28),transparent 70%);}
|
|
.preview svg{position:relative;z-index:1;height:90%;max-width:88%;filter:drop-shadow(0 24px 30px rgba(0,0,0,0.5));}
|
|
.zoomtoggle{position:absolute;top:16px;left:50%;transform:translateX(-50%);z-index:3;display:flex;background:rgba(0,0,0,0.4);border:1px solid var(--border);border-radius:11px;padding:4px;gap:2px;}
|
|
.zoomtoggle button{border:none;background:none;color:var(--muted);font-family:inherit;font-weight:700;font-size:12px;padding:7px 14px;border-radius:8px;cursor:pointer;} .zoomtoggle button.active{background:var(--accent);color:#fff;}
|
|
.brand{position:absolute;top:18px;left:22px;z-index:3;} .brand h1{font-size:18px;margin:0;font-weight:800;letter-spacing:-0.01em;} .brand h1 span{color:var(--accent);} .brand small{color:var(--muted);font-size:11px;}
|
|
|
|
/* Panel */
|
|
.panel{border-left:1px solid var(--border);display:grid;grid-template-rows:auto auto 1fr auto;min-height:0;}
|
|
.p-head{padding:16px 20px 10px;display:flex;align-items:center;gap:12px;} .p-head .money{margin-left:auto;text-align:right;} .p-head .money .l{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;font-weight:800;} .p-head .money .v{font-size:15px;font-weight:800;font-variant-numeric:tabular-nums;}
|
|
.cats{display:flex;gap:4px;padding:0 16px 10px;flex-wrap:wrap;overflow-x:auto;}
|
|
.cats button{border:none;background:var(--slot);color:var(--muted);font-family:inherit;font-weight:700;font-size:12px;padding:9px 11px;border-radius:10px;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;min-width:52px;}
|
|
.cats button .ci{font-size:16px;} .cats button.active{background:var(--accent);color:#fff;} .cats button .dot{width:6px;height:6px;border-radius:50%;background:var(--amber);}
|
|
.p-body{overflow-y:auto;padding:8px 20px;} .p-body::-webkit-scrollbar{width:7px;} .p-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.1);border-radius:4px;}
|
|
.flabel{font-size:10.5px;color:var(--muted);font-weight:800;text-transform:uppercase;letter-spacing:0.05em;margin:14px 2px 8px;display:block;} .flabel:first-child{margin-top:4px;}
|
|
.styles{display:flex;flex-direction:column;gap:7px;}
|
|
.styleopt{display:flex;align-items:center;gap:11px;padding:11px 13px;border-radius:12px;background:var(--slot);border:1px solid var(--border);cursor:pointer;transition:.12s;}
|
|
.styleopt:hover{border-color:var(--accent);} .styleopt.sel{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent) inset;}
|
|
.styleopt .sn{font-weight:700;font-size:13.5px;} .styleopt .sp{margin-left:auto;font-weight:800;font-size:13px;font-variant-numeric:tabular-nums;} .styleopt .sp.free{color:var(--muted);}
|
|
.styleopt .owned{font-size:9.5px;font-weight:800;color:var(--green);background:rgba(52,211,153,0.15);padding:2px 7px;border-radius:999px;margin-left:auto;}
|
|
.swatches{display:flex;flex-wrap:wrap;gap:8px;} .sw{width:32px;height:32px;border-radius:9px;cursor:pointer;border:2px solid transparent;} .sw.sel{border-color:#fff;box-shadow:0 0 0 2px var(--accent);}
|
|
|
|
.p-foot{border-top:1px solid var(--border);padding:14px 20px;}
|
|
.cartline{display:flex;justify-content:space-between;font-size:12px;color:var(--muted);padding:2px 0;} .cartline b{color:var(--fg);}
|
|
.sumrow{display:flex;justify-content:space-between;align-items:baseline;margin:6px 0 10px;} .sumrow .tl{font-weight:800;font-size:15px;} .sumrow .tv{font-weight:800;font-size:20px;font-variant-numeric:tabular-nums;}
|
|
.pay{display:flex;background:var(--slot);border-radius:10px;padding:4px;margin-bottom:10px;} .pay button{flex:1;border:none;background:none;color:var(--muted);font-family:inherit;font-weight:700;font-size:12.5px;padding:8px;border-radius:8px;cursor:pointer;} .pay button.active{background:var(--accent);color:#fff;}
|
|
.foot-actions{display:grid;grid-template-columns:auto 1fr;gap:9px;} .btn{border:none;border-radius:11px;padding:12px 16px;font-size:14px;font-weight:800;cursor:pointer;font-family:inherit;transition:.12s;} .btn.primary{background:var(--accent);color:#fff;} .btn.primary:hover{filter:brightness(1.08);} .btn.ghost{background:var(--surface2);color:var(--fg);border:1px solid var(--border);} .btn:disabled{opacity:.4;cursor:not-allowed;} .btn:active{transform:scale(0.98);}
|
|
|
|
.toast{position:absolute;bottom:18px;left:calc((100% - 400px)/2);transform:translateX(-50%) translateY(16px);background:rgba(12,14,20,0.97);border:1px solid var(--border);color:var(--fg);padding:11px 20px;border-radius:12px;font-size:13.5px;font-weight:600;z-index:50;opacity:0;pointer-events:none;transition:.3s;white-space:nowrap;} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} .toast.warn{border-color:var(--red);} .toast.good{border-color:var(--green);}
|
|
</style>
|
|
|
|
<div class="app">
|
|
<div class="preview">
|
|
<div class="brand"><h1>Aether<span>Threads</span></h1><small>Bekleidung · Los Santos</small></div>
|
|
<div class="zoomtoggle" id="zoomtoggle"><button data-z="ganz" class="active">Ganzkörper</button><button data-z="kopf">Kopf</button></div>
|
|
<div class="floor"></div>
|
|
<div id="avatar"></div>
|
|
</div>
|
|
<div class="panel">
|
|
<div class="p-head"><div><div style="font-weight:800;font-size:15px">Umkleide</div><div style="font-size:11.5px;color:var(--muted)">Anprobieren & kaufen</div></div>
|
|
<div class="money"><div class="l">Bargeld</div><div class="v" id="m-cash"></div></div>
|
|
<div class="money"><div class="l">Konto</div><div class="v" id="m-bank"></div></div></div>
|
|
<div class="cats" id="cats"></div>
|
|
<div class="p-body" id="p-body"></div>
|
|
<div class="p-foot" id="p-foot"></div>
|
|
</div>
|
|
<div class="toast" id="toast"></div>
|
|
</div>
|
|
|
|
<script>
|
|
"use strict";
|
|
const $=id=>document.getElementById(id);
|
|
const euro=n=>n.toLocaleString("de-DE")+" €";
|
|
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 schattiere(hex,f){const n=parseInt(hex.slice(1),16);let r=(n>>16)&255,g=(n>>8)&255,b=n&255;r=Math.max(0,Math.min(255,Math.round(r*f)));g=Math.max(0,Math.min(255,Math.round(g*f)));b=Math.max(0,Math.min(255,Math.round(b*f)));return "#"+((1<<24)+(r<<16)+(g<<8)+b).toString(16).slice(1);}
|
|
|
|
/* =====================================================================
|
|
SORTIMENT
|
|
===================================================================== */
|
|
const farben=[{n:"Schwarz",c:"#22242b"},{n:"Weiß",c:"#e8eaef"},{n:"Grau",c:"#6b7180"},{n:"Navy",c:"#27324f"},{n:"Rot",c:"#b23a3a"},{n:"Grün",c:"#2f6b4a"},{n:"Blau",c:"#3563a8"},{n:"Beige",c:"#c7b18a"},{n:"Gold",c:"#c9a24a"},{n:"Bordeaux",c:"#6b2740"}];
|
|
const kategorien=[
|
|
{id:"kopf", name:"Kopf", icon:"🧢", farbe:true, styles:[{n:"Keine",p:0},{n:"Cap",p:120},{n:"Beanie",p:90},{n:"Hut",p:180},{n:"Helm",p:400}]},
|
|
{id:"brille", name:"Brille", icon:"🕶️", farbe:false, styles:[{n:"Keine",p:0},{n:"Sonnenbrille",p:150},{n:"Brille",p:200}]},
|
|
{id:"maske", name:"Maske", icon:"😷", farbe:true, styles:[{n:"Keine",p:0},{n:"Halbmaske",p:80},{n:"Sturmhaube",p:140}]},
|
|
{id:"oberteil", name:"Oberteil",icon:"👕", farbe:true, styles:[{n:"T-Shirt",p:90},{n:"Hemd",p:160},{n:"Hoodie",p:220},{n:"Jacke",p:380},{n:"Anzug",p:650}]},
|
|
{id:"hose", name:"Hose", icon:"👖", farbe:true, styles:[{n:"Jeans",p:120},{n:"Cargo",p:160},{n:"Shorts",p:80},{n:"Anzughose",p:240}]},
|
|
{id:"schuhe", name:"Schuhe", icon:"👟", farbe:true, styles:[{n:"Sneaker",p:180},{n:"Stiefel",p:260},{n:"Sandalen",p:70}]},
|
|
{id:"kette", name:"Kette", icon:"📿", farbe:false, styles:[{n:"Keine",p:0},{n:"Silberkette",p:300},{n:"Goldkette",p:900}]}
|
|
];
|
|
const spieler={bargeld:6800,konto:52000};
|
|
// getragen = aktuell besessenes Outfit (Basis); outfit = aktuelle Auswahl (Anprobe)
|
|
const getragen={kopf:{s:0,c:0},brille:{s:0,c:0},maske:{s:0,c:0},oberteil:{s:0,c:3},hose:{s:0,c:6},schuhe:{s:0,c:1},kette:{s:0,c:8}};
|
|
let outfit=JSON.parse(JSON.stringify(getragen));
|
|
let aktiveKat="oberteil", zoom="ganz", zahlung="bargeld";
|
|
|
|
const kat=id=>kategorien.find(k=>k.id===id);
|
|
const styleName=id=>kat(id).styles[outfit[id].s].n;
|
|
const styleObj=(id,s)=>kat(id).styles[s];
|
|
const farbe=id=>farben[outfit[id].c].c;
|
|
|
|
/* =====================================================================
|
|
AVATAR-VORSCHAU (bekleidete Figur, front)
|
|
===================================================================== */
|
|
function buildAvatar(){
|
|
const haut="#e8b48f",hautD=schattiere(haut,0.86),haar="#3a2417";
|
|
const topC=farbe('oberteil'),topD=schattiere(topC,0.78),hoseC=farbe('hose'),schuhC=farbe('schuhe');
|
|
const topS=styleName('oberteil'),hoseS=styleName('hose'),schuhS=styleName('schuhe');
|
|
const kopfS=styleName('kopf'),brilleS=styleName('brille'),maskeS=styleName('maske'),ketteS=styleName('kette');
|
|
const langeArm=topS!=="T-Shirt", shorts=hoseS==="Shorts";
|
|
const sturmhaube=maskeS==="Sturmhaube";
|
|
let L="";
|
|
// Beine (Haut-Basis, damit bei Shorts Unterschenkel sichtbar)
|
|
L+=`<path d="M96 250 L118 252 L116 388 L98 388 Z" fill="${haut}"/><path d="M144 250 L122 252 L124 388 L142 388 Z" fill="${haut}"/>`;
|
|
// Hose
|
|
const hoseBot=shorts?312:386;
|
|
L+=`<path d="M92 248 L120 251 L117 ${hoseBot} L96 ${hoseBot} Z" fill="${hoseC}"/><path d="M148 248 L120 251 L123 ${hoseBot} L144 ${hoseBot} Z" fill="${hoseC}"/>`;
|
|
if(hoseS==="Cargo")L+=`<rect x="97" y="300" width="10" height="16" rx="2" fill="${schattiere(hoseC,0.82)}"/><rect x="133" y="300" width="10" height="16" rx="2" fill="${schattiere(hoseC,0.82)}"/>`;
|
|
if(hoseS==="Anzughose")L+=`<line x1="107" y1="256" x2="107" y2="380" stroke="${schattiere(hoseC,0.82)}" stroke-width="1.5"/><line x1="133" y1="256" x2="133" y2="380" stroke="${schattiere(hoseC,0.82)}" stroke-width="1.5"/>`;
|
|
// Schuhe
|
|
if(schuhS==="Stiefel"){L+=`<path d="M92 356 h30 v40 q0 6 -6 6 h-24 Z" fill="${schuhC}"/><path d="M148 356 h-30 v40 q0 6 6 6 h24 Z" fill="${schuhC}"/>`;}
|
|
else if(schuhS==="Sandalen"){L+=`<rect x="90" y="394" width="34" height="8" rx="3" fill="${schuhC}"/><rect x="116" y="394" width="34" height="8" rx="3" fill="${schuhC}"/><path d="M98 388 l12 6 M112 384 l4 10" stroke="${schuhC}" stroke-width="3"/>`;}
|
|
else {L+=`<path d="M88 386 h32 q10 0 10 10 v4 q0 3 -4 3 h-42 q-4 0 -4 -5 Z" fill="${schuhC}"/><path d="M152 386 h-32 q-10 0 -10 10 v4 q0 3 4 3 h42 q4 0 4 -5 Z" fill="${schuhC}"/>`;}
|
|
// Arme (Ärmel = Oberteilfarbe)
|
|
L+=`<rect x="50" y="116" width="18" height="100" rx="9" fill="${topC}"/><rect x="172" y="116" width="18" height="100" rx="9" fill="${topC}"/>`;
|
|
if(!langeArm){ // T-Shirt: Unterarm Haut
|
|
L+=`<rect x="50" y="164" width="18" height="52" rx="9" fill="${haut}"/><rect x="172" y="164" width="18" height="52" rx="9" fill="${haut}"/>`;
|
|
}
|
|
L+=`<circle cx="59" cy="218" r="9" fill="${haut}"/><circle cx="181" cy="218" r="9" fill="${haut}"/>`;
|
|
// Hoodie-Kapuze hinter Hals
|
|
if(topS==="Hoodie")L+=`<path d="M84 108 Q120 96 156 108 L150 130 Q120 118 90 130 Z" fill="${topD}"/>`;
|
|
// Hals + Kopf
|
|
L+=`<rect x="108" y="92" width="24" height="22" fill="${hautD}"/>`;
|
|
// Torso (Oberteil)
|
|
L+=`<path d="M70 112 Q120 98 170 112 L166 252 Q120 262 74 252 Z" fill="${topC}"/>`;
|
|
// Oberteil-Details
|
|
if(topS==="Hemd")L+=`<path d="M120 112 L108 128 L120 140 L132 128 Z" fill="${schattiere(topC,1.12)}"/><line x1="120" y1="140" x2="120" y2="248" stroke="${topD}" stroke-width="2"/>`;
|
|
if(topS==="Hoodie")L+=`<path d="M92 200 h56 v34 q-28 8 -56 0 Z" fill="${topD}"/><line x1="112" y1="118" x2="112" y2="150" stroke="#d8d8d8" stroke-width="2"/><line x1="128" y1="118" x2="128" y2="150" stroke="#d8d8d8" stroke-width="2"/>`;
|
|
if(topS==="Jacke")L+=`<line x1="120" y1="114" x2="120" y2="250" stroke="#3a3a3a" stroke-width="3"/><path d="M104 112 L120 120 L136 112" fill="none" stroke="${topD}" stroke-width="6"/>`;
|
|
if(topS==="Anzug")L+=`<path d="M120 112 L104 118 L118 170 L120 150 L122 170 L136 118 Z" fill="#e8eaef"/><path d="M104 118 L98 200 L118 150 Z" fill="${topD}"/><path d="M136 118 L142 200 L122 150 Z" fill="${topD}"/><path d="M116 150 L120 176 L124 150 Z" fill="#8a1f2f"/>`;
|
|
// Kette
|
|
if(ketteS!=="Keine"){const kc=ketteS==="Goldkette"?"#e6c34d":"#cdd2da";L+=`<path d="M108 112 Q120 150 132 112" fill="none" stroke="${kc}" stroke-width="${ketteS==="Goldkette"?4:2.5}"/><circle cx="120" cy="150" r="${ketteS==="Goldkette"?5:3.5}" fill="${kc}"/>`;}
|
|
// Kopf (Haut)
|
|
if(!sturmhaube)L+=`<circle cx="120" cy="62" r="32" fill="${haut}"/><ellipse cx="89" cy="66" rx="6" ry="9" fill="${hautD}"/><ellipse cx="151" cy="66" rx="6" ry="9" fill="${hautD}"/>`;
|
|
// Haare (falls nicht durch Helm/Sturmhaube/Beanie verdeckt)
|
|
const haarVerdeckt=sturmhaube||kopfS==="Helm";
|
|
if(!haarVerdeckt)L+=`<path d="M89 60 Q120 22 151 60 Q150 40 120 38 Q90 40 89 60 Z" fill="${haar}"/>`;
|
|
// Gesicht
|
|
if(!sturmhaube){
|
|
L+=`<path d="M104 52 q6 -4 12 0" stroke="${schattiere(haar,0.8)}" stroke-width="2.4" fill="none" stroke-linecap="round"/><path d="M124 52 q6 -4 12 0" stroke="${schattiere(haar,0.8)}" stroke-width="2.4" fill="none" stroke-linecap="round"/>`;
|
|
L+=`<ellipse cx="110" cy="60" rx="3.4" ry="2.4" fill="#2a2320"/><ellipse cx="130" cy="60" rx="3.4" ry="2.4" fill="#2a2320"/>`;
|
|
L+=`<path d="M120 64 v7 h-4" stroke="${hautD}" stroke-width="2" fill="none"/>`;
|
|
if(maskeS==="Keine")L+=`<path d="M112 80 q8 5 16 0" stroke="${schattiere(haut,0.6)}" stroke-width="2" fill="none" stroke-linecap="round"/>`;
|
|
} else {
|
|
// Sturmhaube: Kopf in Maskenfarbe, Augenschlitz
|
|
const mc=farbe('maske');L+=`<circle cx="120" cy="62" r="33" fill="${mc}"/><rect x="100" y="55" width="40" height="11" rx="5" fill="#12100e"/><ellipse cx="110" cy="60" rx="3" ry="2.2" fill="#fff"/><ellipse cx="130" cy="60" rx="3" ry="2.2" fill="#fff"/>`;
|
|
}
|
|
// Halbmaske
|
|
if(maskeS==="Halbmaske"){const mc=farbe('maske');L+=`<path d="M100 66 Q120 60 140 66 L138 84 Q120 94 102 84 Z" fill="${mc}"/><path d="M100 66 Q120 72 140 66" fill="none" stroke="${schattiere(mc,0.8)}" stroke-width="1.5"/>`;}
|
|
// Brille
|
|
if(brilleS!=="Keine"){const dark=brilleS==="Sonnenbrille";const lens=dark?"#1a1a1f":"rgba(180,210,230,0.35)";const frame=dark?"#0d0d10":"#2a2a2a";
|
|
L+=`<rect x="101" y="55" width="17" height="11" rx="4" fill="${lens}" stroke="${frame}" stroke-width="2"/><rect x="122" y="55" width="17" height="11" rx="4" fill="${lens}" stroke="${frame}" stroke-width="2"/><line x1="118" y1="59" x2="122" y2="59" stroke="${frame}" stroke-width="2"/>`;}
|
|
// Kopfbedeckung
|
|
const kc=farbe('kopf');
|
|
if(kopfS==="Cap")L+=`<path d="M90 50 Q120 26 150 50 L150 55 Q120 46 90 55 Z" fill="${kc}"/><path d="M120 53 h46 q6 0 6 6 l-52 -2 Z" fill="${schattiere(kc,0.85)}"/>`;
|
|
else if(kopfS==="Beanie")L+=`<path d="M88 58 Q120 22 152 58 Q120 50 88 58 Z" fill="${kc}"/><rect x="88" y="52" width="64" height="9" rx="4" fill="${schattiere(kc,0.85)}"/>`;
|
|
else if(kopfS==="Hut")L+=`<ellipse cx="120" cy="52" rx="52" ry="10" fill="${schattiere(kc,0.85)}"/><path d="M96 52 Q98 26 120 26 Q142 26 144 52 Z" fill="${kc}"/><rect x="96" y="46" width="48" height="7" fill="${schattiere(kc,0.7)}"/>`;
|
|
else if(kopfS==="Helm")L+=`<path d="M86 66 Q86 22 120 22 Q154 22 154 66 Z" fill="${kc}"/><rect x="90" y="54" width="60" height="12" rx="3" fill="#12100e"/><path d="M86 66 h68 v5 h-68 Z" fill="${schattiere(kc,0.75)}"/>`;
|
|
const vb=zoom==="kopf"?"64 18 112 116":"0 0 240 420";
|
|
return `<svg viewBox="${vb}">${L}</svg>`;
|
|
}
|
|
function updateAvatar(){$("avatar").innerHTML=buildAvatar();}
|
|
|
|
/* =====================================================================
|
|
PANEL
|
|
===================================================================== */
|
|
function geld(){$("m-cash").textContent=euro(spieler.bargeld);$("m-bank").textContent=euro(spieler.konto);}
|
|
function veraendert(id){return outfit[id].s!==getragen[id].s||outfit[id].c!==getragen[id].c;}
|
|
function renderCats(){
|
|
$("cats").innerHTML=kategorien.map(k=>`<button data-k="${k.id}" class="${k.id===aktiveKat?'active':''}"><span class="ci">${k.icon}</span>${k.name}${veraendert(k.id)?'<span class="dot"></span>':''}</button>`).join('');
|
|
$("cats").querySelectorAll("[data-k]").forEach(b=>b.addEventListener("click",()=>{aktiveKat=b.dataset.k;renderCats();renderBody();}));
|
|
}
|
|
function renderBody(){
|
|
const k=kat(aktiveKat), o=outfit[aktiveKat];
|
|
let html=`<label class="flabel">${k.name} · Modell</label><div class="styles">`;
|
|
html+=k.styles.map((s,i)=>{const besitzt=getragen[aktiveKat].s===i;
|
|
return `<div class="styleopt ${i===o.s?'sel':''}" data-s="${i}"><span style="font-size:18px">${k.icon}</span><span class="sn">${s.n}</span>${besitzt?'<span class="owned">im Besitz</span>':`<span class="sp ${s.p===0?'free':''}">${s.p===0?'—':euro(s.p)}</span>`}</div>`;}).join('');
|
|
html+=`</div>`;
|
|
if(k.farbe&&k.styles[o.s].n!=="Keine"){
|
|
html+=`<label class="flabel">Farbe</label><div class="swatches">${farben.map((f,i)=>`<div class="sw ${i===o.c?'sel':''}" data-c="${i}" style="background:${f.c}" title="${f.n}"></div>`).join('')}</div>`;
|
|
}
|
|
$("p-body").innerHTML=html;
|
|
$("p-body").querySelectorAll("[data-s]").forEach(el=>el.addEventListener("click",()=>{o.s=+el.dataset.s;renderCats();renderBody();updateAvatar();renderFoot();}));
|
|
$("p-body").querySelectorAll("[data-c]").forEach(el=>el.addEventListener("click",()=>{o.c=+el.dataset.c;renderBody();updateAvatar();renderFoot();}));
|
|
}
|
|
function warenkorb(){
|
|
// geänderte Teile mit Preis > 0
|
|
return kategorien.filter(k=>veraendert(k.id)).map(k=>({id:k.id,name:k.name,teil:styleObj(k.id,outfit[k.id].s),preis:styleObj(k.id,outfit[k.id].s).p})).filter(x=>x.preis>0);
|
|
}
|
|
function renderFoot(){
|
|
const wk=warenkorb(), total=wk.reduce((s,x)=>s+x.preis,0), diff=kategorien.some(k=>veraendert(k.id));
|
|
$("p-foot").innerHTML=`
|
|
${wk.length?wk.map(x=>`<div class="cartline"><b>${x.name}: ${x.teil.n}</b><span>${euro(x.preis)}</span></div>`).join(''):'<div class="cartline" style="justify-content:center;padding:6px 0">Keine Änderungen ausgewählt.</div>'}
|
|
<div class="sumrow"><span class="tl">Gesamt</span><span class="tv">${euro(total)}</span></div>
|
|
<div class="pay" id="pay"><button data-p="bargeld" class="${zahlung==='bargeld'?'active':''}">💵 Bargeld</button><button data-p="karte" class="${zahlung==='karte'?'active':''}">💳 Karte</button></div>
|
|
<div class="foot-actions"><button class="btn ghost" id="reset" ${diff?'':'disabled'}>Zurücksetzen</button><button class="btn primary" id="buy" ${total>0?'':'disabled'}>Kaufen & anziehen · ${euro(total)}</button></div>`;
|
|
$("pay").querySelectorAll("[data-p]").forEach(b=>b.addEventListener("click",()=>{zahlung=b.dataset.p;renderFoot();}));
|
|
$("reset").addEventListener("click",()=>{outfit=JSON.parse(JSON.stringify(getragen));renderCats();renderBody();updateAvatar();renderFoot();toast("Auswahl zurückgesetzt");});
|
|
$("buy").addEventListener("click",kaufen);
|
|
}
|
|
function kaufen(){
|
|
const total=warenkorb().reduce((s,x)=>s+x.preis,0); if(total<=0)return;
|
|
const guthaben=zahlung==="bargeld"?spieler.bargeld:spieler.konto;
|
|
if(total>guthaben){toast(zahlung==="bargeld"?"Nicht genug Bargeld":"Konto nicht gedeckt","warn");return;}
|
|
if(zahlung==="bargeld")spieler.bargeld-=total;else spieler.konto-=total;
|
|
Object.keys(getragen).forEach(id=>{getragen[id]={s:outfit[id].s,c:outfit[id].c};}); // Outfit übernehmen
|
|
geld();renderCats();renderBody();renderFoot();toast("Gekauft & angezogen · "+euro(total),"good");
|
|
}
|
|
|
|
/* Events */
|
|
$("zoomtoggle").querySelectorAll("[data-z]").forEach(b=>b.addEventListener("click",()=>{zoom=b.dataset.z;$("zoomtoggle").querySelectorAll("[data-z]").forEach(x=>x.classList.toggle("active",x.dataset.z===zoom));updateAvatar();}));
|
|
geld();renderCats();renderBody();renderFoot();updateAvatar();
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|