dok/public/js/buildings/arena.js
2026-04-09 18:29:56 +01:00

738 lines
33 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

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

export async function loadArena() {
const ui = document.querySelector(".building-ui");
if (!ui) return;
// Decks sofort laden
let decks = [];
try {
const res = await fetch("/api/decks");
if (res.ok) decks = await res.json();
} catch (e) { console.error("[Arena] Decks:", e); }
const deckOptions = decks.length === 0
? `<option value=""> Erst Deck erstellen </option>`
: `<option value=""> Deck wählen </option>` +
decks.filter(d => d.card_count > 0)
.map(d => `<option value="${d.id}">${d.name} (${d.card_count} Karten)</option>`)
.join("") +
(decks.some(d => d.card_count === 0)
? decks.filter(d => d.card_count === 0)
.map(d => `<option value="" disabled>⚠ ${d.name} (leer)</option>`)
.join("")
: "");
ui.innerHTML = `
<div id="arena-ui">
<!-- Modus-Auswahl -->
<div id="arena-mode-screen">
<div class="arena-title">⚔️ Kampfarena</div>
<p class="arena-subtitle">Wähle Deck und Kampfmodus</p>
<div class="arena-modes">
<div class="arena-mode-wrap">
<div class="arena-mode-card arena-mode-locked" data-mode="1v1">
<div class="arena-mode-icon">🗡️</div>
<div class="arena-mode-label">1v1</div>
<div class="arena-mode-desc">Einzelkampf Duell</div>
</div>
<select class="arena-deck-select" data-mode="1v1">
${deckOptions}
</select>
</div>
<div class="arena-mode-wrap">
<div class="arena-mode-card arena-mode-locked" data-mode="2v2">
<div class="arena-mode-icon">⚔️</div>
<div class="arena-mode-label">2v2</div>
<div class="arena-mode-desc">Team-Kampf</div>
</div>
<select class="arena-deck-select" data-mode="2v2">
${deckOptions}
</select>
</div>
<div class="arena-mode-wrap">
<div class="arena-mode-card arena-mode-locked" data-mode="4v4">
<div class="arena-mode-icon">🛡️</div>
<div class="arena-mode-label">4v4</div>
<div class="arena-mode-desc">Schlachtruf</div>
</div>
<select class="arena-deck-select" data-mode="4v4">
${deckOptions}
</select>
</div>
</div>
<!-- Level-Range Auswahl (nur 1v1) -->
<div class="arena-range-wrap">
<label class="arena-range-label">
<input type="checkbox" id="arena-range-extended">
<span class="arena-range-checkmark"></span>
Gegner bis <strong>±10 Level</strong> suchen
<span class="arena-range-hint">(Standard: ±5 Level)</span>
</label>
</div>
${decks.length === 0
? `<div class="arena-no-deck-hint">⚠️ Kein Deck vorhanden gehe zur <strong>Burg → Kartendeck</strong></div>`
: ""}
<div id="arena-queue-status" style="display:none;"></div>
</div>
<!-- 2v2 Lobby -->
<div id="arena-2v2-screen" style="display:none;">
<div class="arena-lobby-header">
<button class="arena-back-btn" data-back="2v2">← Zurück</button>
<div class="arena-title" style="margin:0;">⚔️ 2v2 Team-Lobby</div>
</div>
<div id="arena-2v2-error" style="display:none;"></div>
<div id="arena-2v2-team-panel" style="display:none;">
<div class="arena-team-box">
<div class="arena-team-title">Dein Team (2v2)</div>
<div id="arena-2v2-team-players"></div>
<div id="arena-2v2-team-actions"></div>
</div>
<div id="arena-2v2-team-status"></div>
</div>
<div id="arena-2v2-lobby-section">
<div class="arena-lobby-actions">
<button class="arena-btn-create" data-create="2v2"> Eigenes Team erstellen</button>
</div>
<div class="arena-lobby-title">Offene Teams</div>
<div id="arena-2v2-lobby-list"><div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div></div>
</div>
</div>
<!-- 4v4 Lobby -->
<div id="arena-4v4-screen" style="display:none;">
<div class="arena-lobby-header">
<button class="arena-back-btn" data-back="4v4">← Zurück</button>
<div class="arena-title" style="margin:0;">🛡️ 4v4 Team-Lobby</div>
</div>
<div id="arena-4v4-error" style="display:none;"></div>
<div id="arena-4v4-team-panel" style="display:none;">
<div class="arena-team-box">
<div class="arena-team-title">Dein Team (4v4)</div>
<div id="arena-4v4-team-players"></div>
<div id="arena-4v4-team-actions"></div>
</div>
<div id="arena-4v4-team-status"></div>
</div>
<div id="arena-4v4-lobby-section">
<div class="arena-lobby-actions">
<button class="arena-btn-create" data-create="4v4"> Eigenes Team erstellen</button>
</div>
<div class="arena-lobby-title">Offene Teams</div>
<div id="arena-4v4-lobby-list"><div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div></div>
</div>
</div>
</div>`;
injectArenaStyles();
initArenaModes();
}
/* ── Styles ─────────────────────────────────────────────── */
function injectArenaStyles() {
if (document.getElementById("arena-popup-styles")) return;
const style = document.createElement("style");
style.id = "arena-popup-styles";
style.textContent = `
@keyframes arenaFadeIn { from{opacity:0} to{opacity:1} }
@keyframes arenaScaleIn { from{transform:scale(0.94);opacity:0} to{transform:scale(1);opacity:1} }
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
#arena-ui { display:flex; flex-direction:column; height:100%; }
/* ── Deck+Mode Layout ── */
.arena-modes { gap:8px; }
.arena-mode-wrap {
flex:1; min-width:120px; max-width:195px;
display:flex; flex-direction:column; gap:6px;
}
.arena-mode-locked {
opacity:.45; cursor:not-allowed !important;
border-color:#3a2810 !important;
}
.arena-mode-locked:hover { transform:none !important; border-color:#3a2810 !important; }
.arena-deck-select {
width:100%; background:#1a0f04; border:1px solid #6b4b2a;
border-radius:6px; color:#f0d9a6; font-family:"Cinzel",serif;
font-size:15px; padding:8px 9px; cursor:pointer;
appearance:none; -webkit-appearance:none;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23a08060'/%3E%3C/svg%3E");
background-repeat:no-repeat; background-position:right 6px center;
padding-right:28px;
}
.arena-deck-select:focus { outline:none; border-color:#c8960c; }
.arena-deck-select option { background:#1a0f04; color:#f0d9a6; }
.arena-no-deck-hint {
margin-top:10px; padding:8px 12px; border-radius:7px;
background:rgba(231,76,60,.1); border:1px solid rgba(231,76,60,.3);
color:#e07060; font-family:"Cinzel",serif; font-size:11px; text-align:center;
}
.arena-no-deck-hint strong { color:#f0c84a; }
#arena-mode-screen { display:flex; flex-direction:column; align-items:center; padding:16px; }
.arena-title { font-family:"Cinzel",serif; font-size:18px; color:#f0d060; letter-spacing:3px; text-align:center; margin-bottom:4px; }
.arena-subtitle { font-family:"Cinzel",serif; font-size:11px; color:#a08060; margin:0 0 14px; }
.arena-modes { display:flex; gap:10px; justify-content:center; flex-wrap:wrap; width:100%; }
.arena-mode-card {
flex:1; min-width:120px; max-width:195px;
background:linear-gradient(180deg,#2a1a08,#1a0f04);
border:2px solid #6b4b2a; border-radius:12px;
padding:21px 12px; cursor:pointer; text-align:center;
transition:border-color .2s,transform .1s;
}
.arena-mode-card:hover { border-color:#c8960c; transform:translateY(-2px); }
.arena-mode-card.searching { opacity:.6; pointer-events:none; border-color:rgba(255,215,80,.5)!important; }
.arena-mode-icon { font-size:36px; margin-bottom:9px; }
.arena-mode-label { font-family:"Cinzel",serif; font-size:19px; color:#f0d060; font-weight:bold; }
.arena-mode-desc { font-size:15px; color:#806040; margin-top:6px; line-height:1.4; }
.arena-range-wrap { margin-top:12px; }
.arena-range-label {
display:flex; align-items:center; gap:8px; cursor:pointer;
font-family:"Cinzel",serif; font-size:11px; color:#a08060;
padding:7px 12px; border:1px solid rgba(200,150,42,.2);
border-radius:7px; transition:border-color .2s,background .2s;
}
.arena-range-label:hover { border-color:rgba(200,150,42,.5); background:rgba(200,150,42,.05); }
.arena-range-label input[type=checkbox] { display:none; }
.arena-range-checkmark {
width:15px; height:15px; border:2px solid #6a4820; border-radius:3px;
display:flex; align-items:center; justify-content:center;
font-size:10px; color:#f0d060; transition:background .2s,border-color .2s; flex-shrink:0;
}
.arena-range-label input:checked ~ .arena-range-checkmark { background:#4a3010; border-color:#c8960c; }
.arena-range-label input:checked ~ .arena-range-checkmark::after { content:"✓"; }
.arena-range-label strong { color:#f0c84a; }
.arena-range-hint { font-size:10px; color:#604830; }
#arena-queue-status {
margin-top:12px; padding:10px 18px; border-radius:10px; width:100%; box-sizing:border-box;
background:rgba(255,215,80,.08); border:1px solid rgba(255,215,80,.3);
color:#dceb15; font-family:"Cinzel",serif; font-size:12px;
letter-spacing:1px; text-align:center; animation:pulse 2s ease-in-out infinite;
}
.qs-cancel { display:inline-block; margin-top:6px; font-size:11px; color:rgba(255,100,100,.8); cursor:pointer; text-decoration:underline; animation:none; }
.qs-cancel:hover { color:#e74c3c; }
#arena-2v2-error,#arena-4v4-error {
padding:8px 12px; border-radius:7px; margin-bottom:8px;
background:rgba(231,76,60,.12); border:1px solid rgba(231,76,60,.4);
color:#e74c3c; font-family:"Cinzel",serif; font-size:11px; text-align:center;
}
#arena-2v2-screen,#arena-4v4-screen { flex-direction:column; gap:12px; padding:14px; height:100%; overflow-y:auto; }
.arena-lobby-header { display:flex; align-items:center; gap:14px; }
.arena-back-btn { background:none; border:1px solid rgba(255,200,80,.3); color:#c8960c; font-family:"Cinzel",serif; font-size:11px; padding:4px 10px; border-radius:4px; cursor:pointer; }
.arena-back-btn:hover { background:rgba(200,150,12,.15); }
.arena-lobby-actions { margin-bottom:4px; }
.arena-btn-create { background:linear-gradient(#4a3018,#2a1a08); border:2px solid #8b6a3c; border-radius:7px; color:#f0d9a6; font-family:"Cinzel",serif; font-size:11px; padding:7px 14px; cursor:pointer; transition:.2s; }
.arena-btn-create:hover { border-color:#f0d060; }
.arena-lobby-title { font-family:"Cinzel",serif; font-size:12px; color:#a08060; letter-spacing:1px; margin-bottom:5px; }
.arena-lobby-empty { font-family:"Cinzel",serif; font-size:11px; color:#606060; padding:10px 0; }
.arena-lobby-row { display:flex; align-items:center; justify-content:space-between; background:linear-gradient(#2a1a08,#1a0f04); border:1px solid #6b4b2a; border-radius:7px; padding:9px 12px; margin-bottom:5px; }
.arena-lobby-row-info { display:flex; align-items:center; gap:10px; }
.arena-lobby-leader { font-family:"Cinzel",serif; font-size:12px; color:#f0d9a6; }
.arena-lobby-level { font-size:10px; color:#a08060; }
.arena-lobby-count { font-size:10px; color:#6a9a4a; }
.arena-btn-join { background:linear-gradient(#1a4a18,#0f2a0e); border:2px solid #4a8a3c; border-radius:5px; color:#a0e090; font-family:"Cinzel",serif; font-size:10px; padding:4px 10px; cursor:pointer; transition:.2s; }
.arena-btn-join:hover { border-color:#8ae060; }
.arena-team-box { background:linear-gradient(#2a1a08,#1a0f04); border:2px solid #6b4b2a; border-radius:9px; padding:12px; }
.arena-team-title { font-family:"Cinzel",serif; font-size:12px; color:#f0d060; letter-spacing:2px; margin-bottom:8px; }
.arena-team-slots { font-family:"Cinzel",serif; font-size:10px; color:#a08060; text-align:center; padding:3px 0 6px; }
.arena-team-player { display:flex; align-items:center; gap:8px; padding:6px 9px; border:1px solid #3a2810; border-radius:5px; margin-bottom:4px; background:rgba(255,255,255,.03); }
.arena-team-player.ready { border-color:#4a8a3c; background:rgba(74,138,60,.1); }
.arena-team-player-name { font-family:"Cinzel",serif; font-size:11px; color:#f0d9a6; flex:1; }
.arena-team-player-level { font-size:10px; color:#a08060; }
.arena-team-player-status { font-size:10px; }
.arena-btn-kick { background:linear-gradient(#4a1010,#2a0808); border:1px solid #8a3030; border-radius:4px; color:#e07070; font-size:10px; font-family:"Cinzel",serif; padding:2px 7px; cursor:pointer; transition:.2s; }
.arena-btn-kick:hover { border-color:#e05050; color:#ff9090; }
.arena-waiting-partner { font-family:"Cinzel",serif; font-size:11px; color:#a08060; text-align:center; padding:7px; animation:pulse 2s ease-in-out infinite; }
.arena-btn-ready { width:100%; margin-top:8px; background:linear-gradient(#1a4a18,#0f2a0e); border:2px solid #4a8a3c; border-radius:7px; color:#a0e090; font-family:"Cinzel",serif; font-size:13px; padding:9px; cursor:pointer; transition:.2s; }
.arena-btn-ready:hover:not([disabled]) { border-color:#8ae060; background:linear-gradient(#2a6a28,#1a3a18); }
.arena-btn-ready.active { border-color:#8ae060; color:#c0f0a0; cursor:default; }
.arena-searching-box { font-family:"Cinzel",serif; font-size:11px; color:#dceb15; text-align:center; padding:9px; border:1px solid rgba(255,215,80,.3); border-radius:7px; margin-top:7px; animation:pulse 2s ease-in-out infinite; }
/* ── Deck-Auswahl ── */
#deck-select-overlay { animation: arenaFadeIn .2s ease; }
.deck-select-box {
background: linear-gradient(#2a1a08, #1a0f04);
border: 2px solid #c8960c;
border-radius: 12px;
padding: 20px;
width: min(360px, 90vw);
max-height: 80vh;
display: flex;
flex-direction: column;
gap: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.8);
}
.deck-select-title {
font-family: "Cinzel", serif;
font-size: 16px;
color: #f0d060;
letter-spacing: 2px;
text-align: center;
}
.deck-select-hint {
font-family: "Cinzel", serif;
font-size: 11px;
color: #a08060;
text-align: center;
margin: 0;
line-height: 1.6;
}
.deck-select-list {
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
max-height: 260px;
}
.deck-select-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: rgba(255,255,255,0.03);
border: 1px solid #3a2810;
border-radius: 7px;
cursor: pointer;
transition: border-color .15s, background .15s;
}
.deck-select-row:not(.deck-empty):hover { border-color: #c8960c; background: rgba(200,150,42,0.08); }
.deck-select-row.deck-selected { border-color: #f0d060; background: rgba(200,150,42,0.12); }
.deck-select-row.deck-empty { opacity: .4; cursor: not-allowed; }
.deck-select-name { font-family: "Cinzel", serif; font-size: 12px; color: #f0d9a6; }
.deck-select-count { font-size: 11px; color: #a08060; }
.deck-select-warn { font-size: 10px; color: #e07070; }
.deck-select-actions { display: flex; gap: 10px; justify-content: flex-end; }
.deck-select-cancel {
background: none;
border: 1px solid rgba(255,200,80,.3);
color: #a08060;
font-family: "Cinzel", serif;
font-size: 11px;
padding: 7px 14px;
border-radius: 6px;
cursor: pointer;
}
.deck-select-cancel:hover { border-color: rgba(255,200,80,.6); color: #c8a060; }
.deck-select-confirm {
background: linear-gradient(#4a3010, #2a1a06);
border: 2px solid #c8960c;
color: #f0d060;
font-family: "Cinzel", serif;
font-size: 11px;
padding: 7px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
transition: .15s;
}
.deck-select-confirm:hover:not(:disabled) { border-color: #f0d060; background: linear-gradient(#5a4020,#3a2a10); }
.deck-btn-disabled { opacity: .4; cursor: not-allowed !important; }
#arena-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.82); backdrop-filter:blur(5px); z-index:9998; animation:arenaFadeIn .25s ease; }
#arena-popup { position:fixed; inset:50px; z-index:9999; display:flex; flex-direction:column; border-radius:14px; overflow:hidden; box-shadow:0 0 0 1px rgba(255,215,80,.35),0 30px 90px rgba(0,0,0,.85); animation:arenaScaleIn .28s cubic-bezier(.22,1,.36,1); }
#arena-popup-titlebar { display:flex; align-items:center; justify-content:space-between; background:rgba(10,8,5,.95); border-bottom:1px solid rgba(255,215,80,.3); padding:0 16px; height:42px; flex-shrink:0; }
#arena-popup-titlebar .ap-title { font-family:"Cinzel",serif; font-size:13px; letter-spacing:4px; color:rgba(255,215,80,.85); text-transform:uppercase; }
#arena-popup-titlebar .ap-url { font-size:11px; color:rgba(255,255,255,.22); }
#arena-popup iframe { flex:1; border:none; width:100%; display:block; }
#match-found-overlay { position:fixed; inset:0; z-index:10000; background:rgba(0,0,0,.9); display:flex; flex-direction:column; align-items:center; justify-content:center; animation:arenaFadeIn .3s ease; }
.mfo-title { font-family:"Cinzel",serif; font-size:36px; color:#ffd750; text-shadow:0 0 30px rgba(255,215,80,.6); letter-spacing:6px; margin-bottom:12px; }
.mfo-vs { font-family:"Cinzel",serif; font-size:18px; color:rgba(255,255,255,.75); letter-spacing:3px; }
.mfo-bar { width:300px; height:4px; background:rgba(255,215,80,.2); border-radius:2px; margin-top:24px; overflow:hidden; }
.mfo-bar-fill { height:100%; background:#ffd750; width:0%; border-radius:2px; transition:width 1.5s ease; }
`;
document.head.appendChild(style);
}
/* ── Socket ────────────────────────────────────────────────*/
function getSocket() { return window._socket || null; }
/* ── State ─────────────────────────────────────────────────*/
let myArenaData = null;
let my2v2TeamId = null;
let my4v4TeamId = null;
let selectedDeckId = null; // Gewähltes Deck für das nächste Match
/* ── Modus-Initialisierung ─────────────────────────────────*/
function initArenaModes() {
// Deck-Dropdown: Card freischalten wenn Deck gewählt
document.querySelectorAll(".arena-deck-select").forEach(select => {
select.addEventListener("change", () => {
const mode = select.dataset.mode;
const card = document.querySelector(`.arena-mode-card[data-mode="${mode}"]`);
if (!card) return;
if (select.value) {
card.classList.remove("arena-mode-locked");
selectedDeckId = Number(select.value);
} else {
card.classList.add("arena-mode-locked");
}
});
});
document.querySelectorAll(".arena-mode-card").forEach(card => {
card.addEventListener("click", () => {
if (card.classList.contains("arena-mode-locked")) return;
// Deck aus dem zugehörigen Dropdown lesen
const mode = card.dataset.mode;
const select = document.querySelector(`.arena-deck-select[data-mode="${mode}"]`);
selectedDeckId = select ? Number(select.value) : null;
if (!selectedDeckId) return;
// Deck-ID für das Battlefield speichern
sessionStorage.setItem("selectedDeckId", selectedDeckId);
if (mode === "1v1") handle1v1Click(card);
else if (mode === "2v2") openTeamLobby("2v2");
else if (mode === "4v4") openTeamLobby("4v4");
});
});
document.addEventListener("click", e => {
const backBtn = e.target.closest(".arena-back-btn");
if (backBtn) {
const mode = backBtn.dataset.back;
if (!mode) return;
leaveTeam(mode);
document.getElementById(`arena-${mode}-screen`).style.display = "none";
document.getElementById("arena-mode-screen").style.display = "";
if (mode === "2v2") my2v2TeamId = null;
if (mode === "4v4") my4v4TeamId = null;
return;
}
const createBtn = e.target.closest(".arena-btn-create");
if (createBtn) {
const mode = createBtn.dataset.create;
const socket = getSocket();
if (!socket) return showModeError(mode, "Keine Verbindung zum Server.");
socket.emit(`create_${mode}_team`, myArenaData);
}
});
}
/* ── 1v1 ───────────────────────────────────────────────────*/
async function handle1v1Click(card) {
const socket = getSocket();
if (!socket) { showArenaError("Keine Verbindung zum Server."); return; }
if (card.classList.contains("searching")) return;
let me;
try {
const res = await fetch("/arena/me");
if (!res.ok) throw new Error("Status " + res.status);
me = await res.json();
} catch {
showArenaError("Spielerdaten konnten nicht geladen werden.");
return;
}
const extended = document.getElementById("arena-range-extended")?.checked;
const levelRange = extended ? 10 : 5;
setCardSearching(card, true);
showQueueStatus(me.level, levelRange);
socket.off("match_found");
socket.off("queue_status");
socket.on("queue_status", data => {
if (data.status === "waiting") showQueueStatus(me.level, levelRange, data.poolSize);
else if (data.status === "left") { setCardSearching(card, false); hideQueueStatus(); }
});
socket.once("match_found", data => {
socket.off("queue_status");
setCardSearching(card, false);
hideQueueStatus();
showMatchFoundOverlay(me.name, data.opponent.name, () => {
openArenaPopup(
`/arena/1v1?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
data.opponent.name, data.matchId
);
});
});
socket.emit("join_1v1", { id: me.id, name: me.name, level: me.level, levelRange });
}
function cancelQueue(card) {
const socket = getSocket();
if (socket) { socket.emit("leave_1v1"); socket.off("match_found"); socket.off("queue_status"); }
setCardSearching(card, false);
hideQueueStatus();
}
/* ── Team-Lobby (2v2 / 4v4) ────────────────────────────────*/
async function openTeamLobby(mode) {
const socket = getSocket();
document.getElementById("arena-mode-screen").style.display = "none";
document.getElementById(`arena-${mode}-screen`).style.display = "flex";
if (!socket) {
showModeError(mode, "Keine Verbindung zum Server. Bitte Seite neu laden.");
return;
}
if (!myArenaData) {
try {
const res = await fetch("/arena/me");
if (!res.ok) throw new Error("HTTP " + res.status);
myArenaData = await res.json();
} catch {
showModeError(mode, "Spielerdaten konnten nicht geladen werden.");
return;
}
}
socket.emit(`get_${mode}_lobbies`);
["lobbies","team_joined","team_update","partner_left","searching",
`match_found_${mode}`,"error","kicked"].forEach(ev => {
const full = ev.startsWith("match_found") ? ev : `${mode}_${ev}`;
socket.off(full);
});
socket.on(`${mode}_lobbies`, list => renderLobbyList(list, socket, mode));
socket.on(`${mode}_team_joined`, data => {
if (mode === "2v2") my2v2TeamId = data.teamId;
if (mode === "4v4") my4v4TeamId = data.teamId;
document.getElementById(`arena-${mode}-team-panel`).style.display = "block";
document.getElementById(`arena-${mode}-lobby-section`).style.display = "none";
hideModeError(mode);
});
socket.on(`${mode}_team_update`, data => renderTeamPanel(data, socket, mode));
socket.on(`${mode}_partner_left`, data => {
const statusEl = document.getElementById(`arena-${mode}-team-status`);
if (statusEl) statusEl.innerHTML = `<span style="color:#e74c3c;">⚠️ ${data.name} hat das Team verlassen.</span>`;
const teamId = mode === "2v2" ? my2v2TeamId : my4v4TeamId;
renderTeamPanel({
teamId, leaderId: null,
players: [{ socketId: socket.id, name: myArenaData.name, level: myArenaData.level, ready: false }],
count: 1, max: mode === "4v4" ? 4 : 2
}, socket, mode);
});
socket.on(`${mode}_searching`, () => {
const s = document.getElementById(`arena-${mode}-team-status`);
const a = document.getElementById(`arena-${mode}-team-actions`);
if (s) s.innerHTML = `<div class="arena-searching-box">⏳ Suche nach Gegnerteam…</div>`;
if (a) a.innerHTML = "";
});
socket.on(`match_found_${mode}`, data => {
socket.off(`${mode}_lobbies`); socket.off(`${mode}_team_update`);
socket.off(`${mode}_partner_left`); socket.off(`${mode}_searching`);
showMatchFoundOverlay(myArenaData.name, `Team ${data.myTeam === 1 ? 2 : 1}`, () => {
document.getElementById(`arena-${mode}-screen`).style.display = "none";
document.getElementById("arena-mode-screen").style.display = "";
openArenaPopup(
`/arena/${mode}?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
data.opponents?.join(" & ") || "Gegner", data.matchId
);
});
});
socket.on(`${mode}_error`, data => showModeError(mode, data.message));
socket.on(`${mode}_kicked`, data => {
if (mode === "2v2") my2v2TeamId = null;
if (mode === "4v4") my4v4TeamId = null;
document.getElementById(`arena-${mode}-team-panel`).style.display = "none";
document.getElementById(`arena-${mode}-lobby-section`).style.display = "block";
socket.emit(`get_${mode}_lobbies`);
showModeError(mode, data.message || "Du wurdest aus dem Team entfernt.");
});
}
/* ── Lobby-Liste rendern ───────────────────────────────────*/
function renderLobbyList(list, socket, mode) {
const el = document.getElementById(`arena-${mode}-lobby-list`);
const max = mode === "4v4" ? 4 : 2;
if (!el) return;
if (!list.length) {
el.innerHTML = `<div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div>`;
return;
}
el.innerHTML = list.map(t => `
<div class="arena-lobby-row">
<div class="arena-lobby-row-info">
<span class="arena-lobby-leader">⚔️ ${t.leader}</span>
<span class="arena-lobby-level">Lvl ${t.leaderLevel}</span>
<span class="arena-lobby-count">${t.count}/${max}</span>
</div>
<button class="arena-btn-join" data-teamid="${t.teamId}">Beitreten</button>
</div>`).join("");
el.querySelectorAll(".arena-btn-join").forEach(btn => {
btn.addEventListener("click", () => {
socket.emit(`join_${mode}_team`, { teamId: btn.dataset.teamid, playerData: myArenaData });
});
});
}
/* ── Team-Panel rendern ────────────────────────────────────*/
function renderTeamPanel(data, socket, mode) {
const playersEl = document.getElementById(`arena-${mode}-team-players`);
const actionsEl = document.getElementById(`arena-${mode}-team-actions`);
if (!playersEl || !actionsEl) return;
const max = data.max || (mode === "4v4" ? 4 : 2);
const teamId = mode === "2v2" ? my2v2TeamId : my4v4TeamId;
const mySocketId = getSocket()?.id;
const iAmLeader = data.leaderId === mySocketId;
const playerRows = data.players.map(p => {
const isLeader = p.socketId === data.leaderId;
const isMe = p.socketId === mySocketId;
const kickBtn = (iAmLeader && !isMe)
? `<button class="arena-btn-kick" data-sid="${p.socketId}" data-tid="${teamId}">Kick</button>`
: "";
return `
<div class="arena-team-player ${p.ready ? "ready" : ""}">
<span class="arena-team-player-name">${p.name}${isLeader ? " 👑" : ""}</span>
<span class="arena-team-player-level">Lvl ${p.level}</span>
<span class="arena-team-player-status">${p.ready ? "✅ Bereit" : "⌛ Wartet"}</span>
${kickBtn}
</div>`;
}).join("");
const emptySlots = Array(max - data.count).fill(0).map(() =>
`<div class="arena-team-player" style="opacity:.3;">
<span class="arena-team-player-name">— Wartet auf Spieler —</span>
</div>`).join("");
playersEl.innerHTML = `<div class="arena-team-slots">${data.count}/${max} Spieler</div>${playerRows}${emptySlots}`;
playersEl.querySelectorAll(".arena-btn-kick").forEach(btn => {
btn.addEventListener("click", () => {
socket.emit(`kick_from_${mode}_team`, { teamId: btn.dataset.tid, targetSocketId: btn.dataset.sid });
});
});
if (data.count < max) {
actionsEl.innerHTML = `<div class="arena-waiting-partner">Warte auf ${max - data.count} weiteren Spieler…</div>`;
} else {
const myEntry = data.players.find(p => p.socketId === mySocketId);
const iAmReady = myEntry?.ready;
actionsEl.innerHTML = iAmReady
? `<button class="arena-btn-ready active" disabled>✅ Du bist bereit</button>`
: `<button class="arena-btn-ready" id="arena-${mode}-ready-btn">⚔️ Bereit</button>`;
if (!iAmReady) {
document.getElementById(`arena-${mode}-ready-btn`)?.addEventListener("click", () => {
socket.emit(`${mode}_player_ready`, { teamId });
});
}
}
}
/* ── Team verlassen ────────────────────────────────────────*/
function leaveTeam(mode) {
const socket = getSocket();
if (!socket) return;
socket.emit(`leave_${mode}_team`);
["lobbies","team_joined","team_update","partner_left","searching",
`match_found_${mode}`,"error","kicked"].forEach(ev => {
socket.off(ev.startsWith("match_found") ? ev : `${mode}_${ev}`);
});
}
/* ── Match-Found Overlay ───────────────────────────────────*/
function showMatchFoundOverlay(myName, opponentName, onDone) {
if (document.getElementById("match-found-overlay")) return;
const overlay = document.createElement("div");
overlay.id = "match-found-overlay";
overlay.innerHTML = `
<div class="mfo-title">⚔️ Match gefunden!</div>
<div class="mfo-vs">${myName} &nbsp;vs&nbsp; ${opponentName}</div>
<div class="mfo-bar"><div class="mfo-bar-fill" id="mfBar"></div></div>`;
document.body.appendChild(overlay);
requestAnimationFrame(() => { const b = document.getElementById("mfBar"); if (b) b.style.width = "100%"; });
setTimeout(() => { overlay.remove(); onDone(); }, 1600);
}
/* ── Popup öffnen ──────────────────────────────────────────*/
function openArenaPopup(src, opponentName, matchId) {
document.getElementById("arena-backdrop")?.remove();
document.getElementById("arena-popup")?.remove();
const backdrop = document.createElement("div"); backdrop.id = "arena-backdrop";
const popup = document.createElement("div"); popup.id = "arena-popup";
popup.innerHTML = `
<div id="arena-popup-titlebar">
<span class="ap-title">⚔️ Arena · vs ${opponentName}</span>
<span class="ap-url">${matchId || src}</span>
</div>
<iframe src="${src}" allowfullscreen></iframe>`;
document.body.appendChild(backdrop);
document.body.appendChild(popup);
}
/* ── UI Hilfsfunktionen ────────────────────────────────────*/
function setCardSearching(card, searching) {
const label = card.querySelector(".arena-mode-label");
const desc = card.querySelector(".arena-mode-desc");
if (searching) {
card.classList.add("searching");
label.textContent = "⏳ Suche…";
desc.textContent = "Warte auf passenden Gegner…";
} else {
card.classList.remove("searching");
label.textContent = "1v1";
desc.textContent = "Einzelkampf Beweis deine Stärke im Duell";
}
}
function showQueueStatus(myLevel, range, poolSize) {
const box = document.getElementById("arena-queue-status");
if (!box) return;
const pool = poolSize ? ` · ${poolSize} im Pool` : "";
box.style.display = "block";
box.innerHTML = `⏳ Suche Gegner (Level ${Math.max(1, myLevel-range)}${myLevel+range})${pool}
<br><span class="qs-cancel" id="qs-cancel-btn">Suche abbrechen</span>`;
document.getElementById("qs-cancel-btn")?.addEventListener("click", () => {
cancelQueue(document.querySelector(".arena-mode-card[data-mode='1v1']"));
});
}
function hideQueueStatus() {
const box = document.getElementById("arena-queue-status");
if (box) box.style.display = "none";
}
function showArenaError(msg) {
const box = document.getElementById("arena-queue-status");
if (!box) return;
box.style.cssText += ";display:block;animation:none;border-color:rgba(231,76,60,.5);color:#e74c3c;";
box.textContent = "❌ " + msg;
setTimeout(() => { box.style.display = "none"; box.style.animation = ""; box.style.borderColor = ""; box.style.color = ""; }, 3000);
}
function showModeError(mode, msg) {
const el = document.getElementById(`arena-${mode}-error`);
if (!el) return;
el.textContent = "❌ " + msg;
el.style.display = "block";
setTimeout(() => { el.style.display = "none"; }, 4000);
}
function hideModeError(mode) {
const el = document.getElementById(`arena-${mode}-error`);
if (el) el.style.display = "none";
}