- 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>
297 lines
29 KiB
HTML
297 lines
29 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>AetherImmos — Immobilien</title>
|
||
<style>
|
||
:root{
|
||
--bg:#080b0d;--fg:#e9f0ee;--muted:#7f8c8a;--surface:rgba(22,28,29,0.72);--surface2:rgba(34,42,43,0.85);
|
||
--slot:#101615;--border:rgba(255,255,255,0.09);--accent:#2fbf7a;--ink:#042113;--accent-soft:rgba(47,191,122,0.15);
|
||
--red:#f0506e;--amber:#f5b23d;--blue:#4aa8ff;--money:#34d39a;
|
||
}
|
||
*{box-sizing:border-box;} html,body{margin:0;height:100%;}
|
||
body{min-height:100%;background:radial-gradient(1100px 700px at 18% 0%,rgba(47,191,122,0.09),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(1220px,97vw);aspect-ratio:16/10;border-radius:22px;overflow:hidden;
|
||
background:linear-gradient(160deg,rgba(20,25,26,0.92),rgba(10,12,13,0.96));backdrop-filter:blur(20px);
|
||
border:1px solid var(--border);box-shadow:0 50px 100px -30px rgba(0,0,0,0.85);display:flex;flex-direction:column;}
|
||
.head{display:flex;align-items:center;gap:14px;padding:15px 24px;border-bottom:1px solid var(--border);}
|
||
.head .logo{width:42px;height:42px;border-radius:12px;display:grid;place-items:center;font-size:20px;background:linear-gradient(150deg,var(--accent),#177a4a);box-shadow:0 8px 20px var(--accent-soft);}
|
||
.head h1{font-size:19px;margin:0;font-weight:800;letter-spacing:-0.02em;} .head h1 span{color:var(--accent);} .head h1 small{display:block;font-size:11px;color:var(--muted);font-weight:500;}
|
||
.switch{display:flex;gap:5px;margin-left:18px;background:var(--slot);border-radius:12px;padding:4px;}
|
||
.switch button{border:none;background:none;color:var(--muted);font-family:inherit;font-weight:700;font-size:13px;padding:8px 15px;border-radius:9px;cursor:pointer;}
|
||
.switch button.active{background:var(--accent);color:var(--ink);}
|
||
.wallet{margin-left:auto;display:flex;gap:10px;} .money-chip{background:var(--surface);border:1px solid var(--border);border-radius:11px;padding:7px 13px;text-align:right;} .money-chip .l{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;font-weight:800;} .money-chip .v{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;}
|
||
|
||
.body{flex:1;overflow-y:auto;padding:18px 24px;} .body::-webkit-scrollbar{width:8px;} .body::-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:21px;margin:0;font-weight:800;letter-spacing:-0.02em;} .phead p{margin:2px 0 0;color:var(--muted);font-size:12.5px;}
|
||
.filters{display:flex;gap:10px;margin-bottom:16px;flex-wrap:wrap;align-items:center;}
|
||
.seg{display:inline-flex;background:var(--slot);border-radius:10px;padding:3px;} .seg button{border:none;background:none;color:var(--muted);font-weight:700;font-size:12.5px;padding:7px 13px;border-radius:8px;cursor:pointer;font-family:inherit;} .seg button.active{background:var(--accent);color:var(--ink);}
|
||
.searchbar{display:flex;align-items:center;gap:9px;background:var(--slot);border:1px solid var(--border);border-radius:10px;padding:9px 13px;flex:1;min-width:180px;} .searchbar input{flex:1;background:none;border:none;color:var(--fg);font-family:inherit;font-size:13.5px;outline:none;}
|
||
|
||
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px;}
|
||
.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;overflow:hidden;cursor:pointer;transition:transform .12s,border-color .12s;}
|
||
.card:hover{transform:translateY(-3px);border-color:var(--accent);}
|
||
.card .photo{aspect-ratio:16/10;position:relative;overflow:hidden;} .card .photo svg{width:100%;height:100%;display:block;}
|
||
.card .ptag{position:absolute;top:10px;left:10px;background:rgba(0,0,0,0.55);color:#fff;font-size:11px;font-weight:800;padding:4px 9px;border-radius:999px;}
|
||
.card .price{position:absolute;bottom:10px;right:10px;background:var(--accent);color:var(--ink);font-size:15px;font-weight:800;padding:5px 11px;border-radius:9px;font-variant-numeric:tabular-nums;}
|
||
.card .cinfo{padding:13px 15px;} .card .ct{font-size:15px;font-weight:800;} .card .cl{font-size:12.5px;color:var(--muted);margin-top:1px;}
|
||
.card .cstats{display:flex;gap:14px;margin-top:9px;font-size:12px;color:var(--muted);font-weight:600;} .card .cstats b{color:var(--fg);}
|
||
|
||
/* Detail */
|
||
.detail-top{display:grid;grid-template-columns:1.4fr 1fr;gap:20px;align-items:start;}
|
||
.detail-photo{border-radius:16px;overflow:hidden;border:1px solid var(--border);} .detail-photo svg{width:100%;display:block;}
|
||
.detail-side .price-big{font-size:30px;font-weight:800;font-variant-numeric:tabular-nums;} .detail-side .price-big small{font-size:13px;color:var(--muted);font-weight:600;}
|
||
.kv{display:flex;justify-content:space-between;font-size:13px;padding:7px 0;border-bottom:1px solid var(--border);} .kv:last-child{border:none;} .kv span:first-child{color:var(--muted);}
|
||
.chips{display:flex;flex-wrap:wrap;gap:7px;margin-top:6px;} .chip{background:var(--slot);border:1px solid var(--border);border-radius:999px;padding:5px 11px;font-size:11.5px;font-weight:600;display:inline-flex;gap:6px;align-items:center;}
|
||
.chip::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--accent);}
|
||
.desc{color:#c2cdca;font-size:13.5px;line-height:1.6;margin-top:12px;}
|
||
|
||
.card2{background:var(--surface);border:1px solid var(--border);border-radius:15px;padding:16px;}
|
||
.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;}
|
||
.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 .av{width:34px;height:34px;border-radius:50%;display:grid;place-items:center;font-weight:800;color:#fff;flex-shrink:0;} .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);}
|
||
.pill{font-size:10px;font-weight:800;padding:3px 8px;border-radius:999px;} .pill.own{background:rgba(47,191,122,0.16);color:var(--accent);} .pill.key{background:rgba(245,178,61,0.16);color:var(--amber);}
|
||
|
||
.btn{border:none;border-radius:11px;padding:12px 18px;font-size:14px;font-weight:800;cursor:pointer;font-family:inherit;transition:.12s;} .btn.primary{background:var(--accent);color:var(--ink);} .btn.primary:hover{filter:brightness(1.08);} .btn.ghost{background:var(--surface2);color:var(--fg);border:1px solid var(--border);} .btn.red{background:var(--red);color:#2a0910;} .btn.sm{padding:8px 13px;font-size:12.5px;} .btn:active{transform:scale(0.98);} .btn:disabled{opacity:.4;cursor:not-allowed;}
|
||
.back{background:var(--surface2);border:1px solid var(--border);color:var(--fg);border-radius:10px;padding:8px 13px;font-family:inherit;font-weight:700;cursor:pointer;}
|
||
.input,select.input{width:100%;background:var(--slot);border:1px solid var(--border);border-radius:10px;padding:11px 12px;color:var(--fg);font-size:14px;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:12px 2px 6px;display:block;}
|
||
|
||
.overlay{position:absolute;inset:0;z-index:40;background:rgba(4,6,8,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:96%;max-height:90%;overflow-y:auto;background:rgba(18,22,23,0.98);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:0 30px 70px -18px rgba(0,0,0,0.8);}
|
||
.modal h3{margin:0 0 3px;font-size:19px;} .modal .msub{color:var(--muted);font-size:12.5px;margin-bottom:14px;} .modal-actions{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin-top:16px;}
|
||
.pay{display:flex;background:var(--slot);border-radius:10px;padding:4px;margin-top:6px;} .pay button{flex:1;border:none;background:none;color:var(--muted);font-family:inherit;font-weight:700;font-size:13px;padding:9px;border-radius:8px;cursor:pointer;} .pay button.active{background:var(--accent);color:var(--ink);}
|
||
.chk{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:9px;background:var(--slot);border:1px solid var(--border);margin-bottom:5px;cursor:pointer;} .chk .av{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;font-weight:800;color:#fff;font-size:11px;}
|
||
|
||
.toast{position:absolute;bottom:18px;left:50%;transform:translateX(-50%) translateY(16px);background:rgba(12,16,17,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;} .toast.show{opacity:1;transform:translateX(-50%) translateY(0);} .toast.warn{border-color:var(--red);} .toast.good{border-color:var(--accent);}
|
||
.empty{text-align:center;color:var(--muted);padding:44px 10px;} .empty .big{font-size:44px;margin-bottom:8px;}
|
||
</style>
|
||
|
||
<div class="app">
|
||
<div class="head"><span class="logo">🏡</span><h1>Aether<span>Immos</span><small>Immobilien in Los Santos</small></h1>
|
||
<div class="switch" id="switch"></div>
|
||
<div class="wallet"><div class="money-chip"><div class="l">Bargeld</div><div class="v" id="w-cash"></div></div><div class="money-chip"><div class="l">Konto</div><div class="v" id="w-bank"></div></div></div>
|
||
</div>
|
||
<div class="body" id="body"></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=>n.toLocaleString("de-DE")+" €";
|
||
const initialen=n=>n.replace(/\s*\(.*\)/,"").split(" ").map(w=>w[0]).slice(0,2).join("");
|
||
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){$("overlay").innerHTML=`<div class="modal">${html}</div>`;$("overlay").classList.add("show");}
|
||
function closeModal(){$("overlay").classList.remove("show");$("overlay").innerHTML="";}
|
||
|
||
/* =====================================================================
|
||
DATEN
|
||
===================================================================== */
|
||
const OWNER="Max Weber";
|
||
const RUECKKAUF=0.6; // Verkauf gibt 60% des Kaufpreises zurück
|
||
const START_SCHLUESSEL=5; // Anzahl Schlüssel beim Kauf
|
||
const NACHMACH_GEBUEHR=250; // Kosten fürs Nachmachen (Schlüsseldienst)
|
||
const spieler={bargeld:45000,konto:380000};
|
||
// Bekannte Personen (Schlüssel-Vergabe)
|
||
const kontakte=["Lena Berger","Ben Fischer","Karl Mendez","Sofia Klein"];
|
||
|
||
// Marktangebote (AetherImmos)
|
||
const marktplatz=[
|
||
{id:"m1",titel:"Apartment Vinewood",typ:"Wohnung",ort:"Vinewood Blvd",preis:85000,zimmer:2,flaeche:65,garage:1,farbe:"#2f7bbf",features:["Balkon","Möbliert","Aufzug"],beschreibung:"Helle 2-Zimmer-Wohnung mit Balkon im Herzen von Vinewood. Ideal für Einsteiger."},
|
||
{id:"m2",titel:"Stadthaus Mirror Park",typ:"Haus",ort:"Mirror Park",preis:210000,zimmer:4,flaeche:140,garage:2,farbe:"#bf6a2f",features:["Garten","Garage","Kamin"],beschreibung:"Charmantes Stadthaus mit Garten und Doppelgarage in ruhiger Lage."},
|
||
{id:"m3",titel:"Villa Vinewood Hills",typ:"Villa",ort:"Vinewood Hills",preis:1200000,zimmer:7,flaeche:420,garage:4,farbe:"#8a6a34",features:["Pool","Meerblick","Sicherheit","Garage"],beschreibung:"Exklusive Luxusvilla mit Infinity-Pool, Panoramablick und Sicherheitsanlage."},
|
||
{id:"m4",titel:"Loft Downtown",typ:"Wohnung",ort:"Downtown",preis:145000,zimmer:1,flaeche:90,garage:0,farbe:"#5a5a66",features:["Dachterrasse","Möbliert"],beschreibung:"Modernes Industrie-Loft mit offener Raumaufteilung und Dachterrasse."},
|
||
{id:"m5",titel:"Gewerbe Legion Square",typ:"Gewerbe",ort:"Legion Square",preis:320000,zimmer:0,flaeche:200,garage:1,farbe:"#2f9e73",features:["Ladenfläche","Lager","Vitrine"],beschreibung:"Attraktive Gewerbefläche in Top-Lauflage – perfekt für Einzelhandel."},
|
||
{id:"m6",titel:"Bungalow Sandy Shores",typ:"Haus",ort:"Sandy Shores",preis:68000,zimmer:3,flaeche:110,garage:1,farbe:"#c79a5a",features:["Garten","Werkstatt"],beschreibung:"Gemütlicher Bungalow mit Werkstatt – viel Platz fürs Geld."},
|
||
{id:"m7",titel:"Penthouse Del Perro",typ:"Wohnung",ort:"Del Perro",preis:890000,zimmer:3,flaeche:210,garage:2,farbe:"#7c5cf6",features:["Pool","Meerblick","Aufzug","Sicherheit"],beschreibung:"Spektakuläres Penthouse mit privatem Pool und atemberaubendem Meerblick."}
|
||
];
|
||
// Eigene Immobilien (mit Schlüssel-Zugang; Schlüssel = Item im Inventar)
|
||
const meine=[
|
||
{id:"o1",titel:"Apartment 4B",typ:"Wohnung",ort:"Vespucci Blvd",kaufpreis:95000,zimmer:2,flaeche:70,garage:1,farbe:"#2f7bbf",features:["Balkon","Garage","Möbliert"],beschreibung:"Deine erste eigene Wohnung in Vespucci.",schluessel:5,gekauft:"20.05.2026"}
|
||
];
|
||
|
||
let ansicht="markt"; // "markt" | "meine"
|
||
let filterTyp="Alle";
|
||
let suche="";
|
||
let marktDetail=null; // geöffnetes Angebot
|
||
let meineDetail=null; // geöffnete eigene Immobilie
|
||
|
||
/* =====================================================================
|
||
IMMOBILIEN-ILLUSTRATION (stilisiertes "Foto" je Typ)
|
||
===================================================================== */
|
||
function immoSvg(typ,farbe){
|
||
const dach=schattiere(farbe,0.62), wand=farbe, wandD=schattiere(farbe,0.82), glas="#bfe0f0", glasD="#8fbcd6";
|
||
const himmel=`<rect width="300" height="188" fill="url(#sky)"/><circle cx="252" cy="40" r="20" fill="#ffe3a0" opacity="0.8"/><rect y="150" width="300" height="38" fill="#20302a"/>`;
|
||
const fenster=(x,y,w,h)=>`<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="2" fill="${glas}"/><rect x="${x}" y="${y}" width="${w}" height="${h}" rx="2" fill="none" stroke="${glasD}" stroke-width="1"/><line x1="${x+w/2}" y1="${y}" x2="${x+w/2}" y2="${y+h}" stroke="${glasD}" stroke-width="1"/>`;
|
||
let g="";
|
||
if(typ==="Villa"){
|
||
g=`<rect x="34" y="88" width="232" height="64" fill="${wand}"/><rect x="34" y="82" width="232" height="8" fill="${dach}"/>
|
||
<rect x="44" y="100" width="46" height="44" fill="${wandD}"/>${fenster(100,100,40,30)}${fenster(150,100,40,30)}${fenster(200,100,40,30)}
|
||
<rect x="120" y="120" width="24" height="32" fill="${dach}"/><ellipse cx="70" cy="150" rx="34" ry="7" fill="#2a7bbf" opacity="0.55"/>`;
|
||
} else if(typ==="Haus"){
|
||
g=`<path d="M60 98 L150 56 L240 98 Z" fill="${dach}"/><rect x="78" y="98" width="144" height="54" fill="${wand}"/>
|
||
<rect x="132" y="118" width="26" height="34" fill="${dach}"/>${fenster(92,110,30,24)}${fenster(178,110,30,24)}<rect x="196" y="60" width="12" height="24" fill="${dach}"/>`;
|
||
} else if(typ==="Gewerbe"){
|
||
g=`<rect x="40" y="86" width="220" height="66" fill="${wand}"/><rect x="40" y="80" width="220" height="10" fill="${dach}"/>
|
||
<rect x="50" y="120" width="200" height="32" fill="${glasD}"/>${fenster(56,124,40,24)}${fenster(102,124,40,24)}${fenster(148,124,40,24)}${fenster(194,124,40,24)}
|
||
<path d="M46 108 h208 l-10 12 h-188 Z" fill="${schattiere(farbe,1.1)}"/><rect x="120" y="90" width="60" height="14" rx="3" fill="${dach}"/>`;
|
||
} else { // Wohnung / Penthouse
|
||
g=`<rect x="70" y="46" width="160" height="106" fill="${wand}"/><rect x="70" y="42" width="160" height="8" fill="${dach}"/>`;
|
||
for(let r=0;r<4;r++)for(let c=0;c<4;c++)g+=fenster(84+c*38,56+r*24,26,16);
|
||
g+=`<rect x="132" y="130" width="30" height="22" fill="${dach}"/>`;
|
||
}
|
||
return `<svg viewBox="0 0 300 188"><defs><linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#25506b"/><stop offset="100%" stop-color="#3a7a8a"/></linearGradient></defs>${himmel}${g}</svg>`;
|
||
}
|
||
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);}
|
||
|
||
/* =====================================================================
|
||
SHELL
|
||
===================================================================== */
|
||
function renderSwitch(){
|
||
$("switch").innerHTML=`<button data-a="markt" class="${ansicht==='markt'?'active':''}">Markt</button><button data-a="meine" class="${ansicht==='meine'?'active':''}">Meine Immobilien${meine.length?' ('+meine.length+')':''}</button>`;
|
||
$("switch").querySelectorAll("[data-a]").forEach(b=>b.addEventListener("click",()=>{ansicht=b.dataset.a;marktDetail=null;meineDetail=null;render();}));
|
||
}
|
||
function aktualisiereGeld(){$("w-cash").textContent=euro(spieler.bargeld);$("w-bank").textContent=euro(spieler.konto);}
|
||
function render(){renderSwitch();aktualisiereGeld();(ansicht==="markt"?renderMarkt:renderMeine)();}
|
||
|
||
/* =====================================================================
|
||
MARKT (AetherImmos)
|
||
===================================================================== */
|
||
function renderMarkt(){
|
||
if(marktDetail){renderMarktDetail();return;}
|
||
const typen=["Alle","Wohnung","Haus","Villa","Gewerbe"];
|
||
const liste=marktplatz.filter(m=>(filterTyp==="Alle"||m.typ===filterTyp)&&(!suche||(m.titel+" "+m.ort).toLowerCase().includes(suche)));
|
||
$("body").innerHTML=`<div class="phead"><div><h2>Immobilienmarkt</h2><p>${marktplatz.length} Angebote in Los Santos</p></div></div>
|
||
<div class="filters"><div class="seg" id="f-typ">${typen.map(t=>`<button data-t="${t}" class="${t===filterTyp?'active':''}">${t}</button>`).join('')}</div>
|
||
<div class="searchbar"><span>🔎</span><input id="f-such" placeholder="Ort oder Titel suchen…" value="${sicher(suche)}"></div></div>
|
||
<div class="grid" id="markt-grid">${liste.map(marktCard).join('')||'<div class="empty" style="grid-column:1/-1"><div class="big">🏙️</div>Keine passenden Angebote.</div>'}</div>`;
|
||
$("f-typ").querySelectorAll("[data-t]").forEach(b=>b.addEventListener("click",()=>{filterTyp=b.dataset.t;renderMarkt();}));
|
||
const s=$("f-such");s.addEventListener("input",()=>{suche=s.value.trim().toLowerCase();const g=$("markt-grid");const l=marktplatz.filter(m=>(filterTyp==="Alle"||m.typ===filterTyp)&&(!suche||(m.titel+" "+m.ort).toLowerCase().includes(suche)));g.innerHTML=l.map(marktCard).join('')||'<div class="empty" style="grid-column:1/-1">Keine Treffer.</div>';bindMarktCards();});
|
||
bindMarktCards();
|
||
}
|
||
function marktCard(m){return `<div class="card" data-m="${m.id}"><div class="photo">${immoSvg(m.typ,m.farbe)}<span class="ptag">${m.typ}</span><span class="price">${euro(m.preis)}</span></div>
|
||
<div class="cinfo"><div class="ct">${sicher(m.titel)}</div><div class="cl">📍 ${sicher(m.ort)}</div>
|
||
<div class="cstats">${m.zimmer?`<span><b>${m.zimmer}</b> Zi.</span>`:''}<span><b>${m.flaeche}</b> m²</span><span><b>${m.garage}</b> 🚗</span></div></div></div>`;}
|
||
function bindMarktCards(){$("body").querySelectorAll("[data-m]").forEach(c=>c.addEventListener("click",()=>{marktDetail=c.dataset.m;renderMarktDetail();}));}
|
||
function renderMarktDetail(){
|
||
const m=marktplatz.find(x=>x.id===marktDetail);
|
||
$("body").innerHTML=`<div class="phead"><div style="display:flex;align-items:center;gap:12px"><button class="back" id="md-back">‹ Markt</button><h2 style="margin:0">${sicher(m.titel)}</h2></div></div>
|
||
<div class="detail-top"><div class="detail-photo">${immoSvg(m.typ,m.farbe)}</div>
|
||
<div class="detail-side"><div class="price-big">${euro(m.preis)} <small>Kaufpreis</small></div>
|
||
<div style="margin:14px 0">
|
||
<div class="kv"><span>Typ</span><span>${m.typ}</span></div><div class="kv"><span>Lage</span><span>${sicher(m.ort)}</span></div>
|
||
<div class="kv"><span>Zimmer</span><span>${m.zimmer||'—'}</span></div><div class="kv"><span>Wohnfläche</span><span>${m.flaeche} m²</span></div>
|
||
<div class="kv"><span>Garagenplätze</span><span>${m.garage}</span></div></div>
|
||
<button class="btn primary" id="md-buy" style="width:100%">Kaufen · ${euro(m.preis)}</button></div></div>
|
||
<div class="card2" style="margin-top:16px"><div class="list-h">Ausstattung</div><div class="chips">${m.features.map(f=>`<span class="chip">${sicher(f)}</span>`).join('')}</div>
|
||
<div class="desc">${sicher(m.beschreibung)}</div></div>`;
|
||
$("md-back").addEventListener("click",()=>{marktDetail=null;renderMarkt();});
|
||
$("md-buy").addEventListener("click",()=>kaufMenu(m));
|
||
}
|
||
|
||
/* =====================================================================
|
||
KAUFMENÜ (separat)
|
||
===================================================================== */
|
||
let kaufZahlung="konto";
|
||
function kaufMenu(m){
|
||
kaufZahlung="konto";
|
||
const notar=Math.round(m.preis*0.02);
|
||
modal(`<h3>Kaufmenü</h3><div class="msub">${sicher(m.titel)} · ${sicher(m.ort)}</div>
|
||
<div style="border-radius:12px;overflow:hidden;border:1px solid var(--border);margin-bottom:12px">${immoSvg(m.typ,m.farbe)}</div>
|
||
<div class="kv"><span>Kaufpreis</span><span>${euro(m.preis)}</span></div>
|
||
<div class="kv"><span>Notar- & Bearbeitungsgebühr (2%)</span><span>${euro(notar)}</span></div>
|
||
<div class="kv" style="font-weight:800"><span>Gesamt</span><span>${euro(m.preis+notar)}</span></div>
|
||
<div style="font-size:11.5px;color:var(--muted);margin-top:8px">Beim Kauf erhältst du <b>${START_SCHLUESSEL} Hausschlüssel (Items)</b>, die du nachmachen und weitergeben kannst.</div>
|
||
<label class="flabel">Zahlung</label><div class="pay" id="k-pay"><button data-p="konto" class="active">💳 Konto (${euro(spieler.konto)})</button><button data-p="bargeld">💵 Bargeld (${euro(spieler.bargeld)})</button></div>
|
||
<div class="modal-actions"><button class="btn ghost" id="k-cancel">Abbrechen</button><button class="btn primary" id="k-ok">Kauf bestätigen</button></div>`);
|
||
const gesamt=m.preis+notar;
|
||
$("k-pay").querySelectorAll("[data-p]").forEach(b=>b.addEventListener("click",()=>{kaufZahlung=b.dataset.p;$("k-pay").querySelectorAll("[data-p]").forEach(x=>x.classList.remove("active"));b.classList.add("active");}));
|
||
$("k-cancel").addEventListener("click",closeModal);
|
||
$("k-ok").addEventListener("click",()=>{
|
||
const guthaben=kaufZahlung==="konto"?spieler.konto:spieler.bargeld;
|
||
if(gesamt>guthaben){toast(kaufZahlung==="konto"?"Konto nicht gedeckt":"Nicht genug Bargeld","warn");return;}
|
||
if(kaufZahlung==="konto")spieler.konto-=gesamt;else spieler.bargeld-=gesamt;
|
||
// Angebot vom Markt entfernen, in Besitz überführen, Schlüssel-Item vergeben
|
||
marktplatz.splice(marktplatz.findIndex(x=>x.id===m.id),1);
|
||
meine.push({id:"o"+Date.now(),titel:m.titel,typ:m.typ,ort:m.ort,kaufpreis:m.preis,zimmer:m.zimmer,flaeche:m.flaeche,garage:m.garage,farbe:m.farbe,features:m.features,beschreibung:m.beschreibung,schluessel:START_SCHLUESSEL,gekauft:new Date().toLocaleDateString('de-DE')});
|
||
closeModal();toast("🏡 "+m.titel+" gekauft! "+START_SCHLUESSEL+" Schlüssel erhalten.","good");
|
||
ansicht="meine";marktDetail=null;render();
|
||
});
|
||
}
|
||
|
||
/* =====================================================================
|
||
MEINE IMMOBILIEN (Verwaltung, Zugang/Schlüssel, Verkauf)
|
||
===================================================================== */
|
||
function renderMeine(){
|
||
if(meineDetail){renderMeineDetail();return;}
|
||
$("body").innerHTML=`<div class="phead"><div><h2>Meine Immobilien</h2><p>${meine.length} in deinem Besitz</p></div></div>
|
||
${meine.length?`<div class="grid">${meine.map(o=>`<div class="card" data-o="${o.id}"><div class="photo">${immoSvg(o.typ,o.farbe)}<span class="ptag">${o.typ}</span></div>
|
||
<div class="cinfo"><div class="ct">${sicher(o.titel)}</div><div class="cl">📍 ${sicher(o.ort)} · ${o.schluessel} Schlüssel</div>
|
||
<div class="cstats">${o.zimmer?`<span><b>${o.zimmer}</b> Zi.</span>`:''}<span><b>${o.flaeche}</b> m²</span><span><b>${o.garage}</b> 🚗</span></div></div></div>`).join('')}</div>`
|
||
:'<div class="empty"><div class="big">🏠</div>Du besitzt noch keine Immobilie.<div style="margin-top:12px"><button class="btn primary" id="zum-markt">Zum Markt</button></div></div>'}`;
|
||
$("body").querySelectorAll("[data-o]").forEach(c=>c.addEventListener("click",()=>{meineDetail=c.dataset.o;renderMeineDetail();}));
|
||
const zm=$("zum-markt");if(zm)zm.addEventListener("click",()=>{ansicht="markt";render();});
|
||
}
|
||
function renderMeineDetail(){
|
||
const o=meine.find(x=>x.id===meineDetail);
|
||
const verkaufswert=Math.round(o.kaufpreis*RUECKKAUF);
|
||
$("body").innerHTML=`<div class="phead"><div style="display:flex;align-items:center;gap:12px"><button class="back" id="o-back">‹ Meine Immobilien</button><h2 style="margin:0">${sicher(o.titel)}</h2><span class="pill own">Eigentum</span></div>
|
||
<button class="btn primary" id="o-enter">Betreten</button></div>
|
||
<div class="detail-top"><div class="detail-photo">${immoSvg(o.typ,o.farbe)}</div>
|
||
<div class="detail-side">
|
||
<div class="kv"><span>Typ</span><span>${o.typ} · ${sicher(o.ort)}</span></div>
|
||
<div class="kv"><span>Wohnfläche</span><span>${o.flaeche} m² · ${o.zimmer||'—'} Zi.</span></div>
|
||
<div class="kv"><span>Gekauft am</span><span>${o.gekauft} für ${euro(o.kaufpreis)}</span></div>
|
||
<div class="kv"><span>Verkaufswert (${Math.round(RUECKKAUF*100)}%)</span><span style="color:var(--money);font-weight:800">${euro(verkaufswert)}</span></div>
|
||
<div class="chips" style="margin-top:12px">${o.features.map(f=>`<span class="chip">${sicher(f)}</span>`).join('')}</div>
|
||
<button class="btn red" id="o-sell" style="width:100%;margin-top:16px">Verkaufen · ${euro(verkaufswert)} zurück</button></div></div>
|
||
<div class="card2" style="margin-top:16px"><div class="list-h">Schlüssel (Items) <span class="r">Du hältst: <b style="color:var(--amber)">${o.schluessel}</b></span></div>
|
||
<div class="row" style="margin:0"><span class="av" style="background:#f5b23d;color:#3a2600">🔑</span>
|
||
<div class="grow"><div class="t">Hausschlüssel „${sicher(o.titel)}“</div><div class="m">Kann nachgemacht und als Item weitergegeben werden</div></div></div>
|
||
<div style="display:flex;gap:9px;margin-top:10px"><button class="btn primary sm" id="o-copy">+ Nachmachen · ${euro(NACHMACH_GEBUEHR)}</button></div>
|
||
<div style="font-size:11.5px;color:var(--muted);margin-top:8px">Beim Kauf erhältst du ${START_SCHLUESSEL} Schlüssel. „Nachmachen" erstellt beim Schlüsseldienst eine Kopie. Weitergeben an andere geschieht über das Inventar (Schlüssel = Item).</div></div>`;
|
||
$("o-back").addEventListener("click",()=>{meineDetail=null;renderMeine();});
|
||
$("o-enter").addEventListener("click",()=>toast("🚪 "+o.titel+" betreten"));
|
||
$("o-sell").addEventListener("click",()=>verkaufFrage(o));
|
||
$("o-copy").addEventListener("click",()=>{
|
||
if(spieler.konto>=NACHMACH_GEBUEHR)spieler.konto-=NACHMACH_GEBUEHR;
|
||
else if(spieler.bargeld>=NACHMACH_GEBUEHR)spieler.bargeld-=NACHMACH_GEBUEHR;
|
||
else {toast("Nicht genug Geld für den Schlüsseldienst","warn");return;}
|
||
o.schluessel++;toast("🔑 Schlüssel nachgemacht","good");aktualisiereGeld();renderMeineDetail();});
|
||
}
|
||
function verkaufFrage(o){
|
||
const wert=Math.round(o.kaufpreis*RUECKKAUF);
|
||
modal(`<h3>Immobilie verkaufen</h3><div class="msub">${sicher(o.titel)}</div>
|
||
<div class="kv"><span>Ursprünglicher Kaufpreis</span><span>${euro(o.kaufpreis)}</span></div>
|
||
<div class="kv"><span>Rückkaufquote</span><span>${Math.round(RUECKKAUF*100)} %</span></div>
|
||
<div class="kv" style="font-weight:800"><span>Du erhältst</span><span style="color:var(--money)">${euro(wert)}</span></div>
|
||
<div style="font-size:11.5px;color:var(--muted);margin-top:8px">Der Verkauf erstattet nur einen Teil des Kaufpreises. Alle Schlüssel werden ungültig.</div>
|
||
<label class="flabel">Auszahlung auf</label><div class="pay" id="v-pay"><button data-p="konto" class="active">💳 Konto</button><button data-p="bargeld">💵 Bargeld</button></div>
|
||
<div class="modal-actions"><button class="btn ghost" id="v-cancel">Abbrechen</button><button class="btn red" id="v-ok">Verkaufen</button></div>`);
|
||
let ziel="konto";
|
||
$("v-pay").querySelectorAll("[data-p]").forEach(b=>b.addEventListener("click",()=>{ziel=b.dataset.p;$("v-pay").querySelectorAll("[data-p]").forEach(x=>x.classList.remove("active"));b.classList.add("active");}));
|
||
$("v-cancel").addEventListener("click",closeModal);
|
||
$("v-ok").addEventListener("click",()=>{
|
||
if(ziel==="konto")spieler.konto+=wert;else spieler.bargeld+=wert;
|
||
meine.splice(meine.findIndex(x=>x.id===o.id),1);
|
||
closeModal();toast("Verkauft für "+euro(wert)+" · Schlüssel ungültig","good");
|
||
meineDetail=null;render();
|
||
});
|
||
}
|
||
|
||
$("overlay").addEventListener("click",e=>{if(e.target===$("overlay"))closeModal();});
|
||
render();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|