From ba987e55648b5060490b33c9e3f50bad9b65e8c3 Mon Sep 17 00:00:00 2001 From: cay Date: Mon, 13 Apr 2026 18:59:58 +0100 Subject: [PATCH] dygnydf --- public/js/buildings/1v1.js | 833 ++++++++++++++++++++++--------------- 1 file changed, 486 insertions(+), 347 deletions(-) diff --git a/public/js/buildings/1v1.js b/public/js/buildings/1v1.js index fc849ba..f4b0237 100644 --- a/public/js/buildings/1v1.js +++ b/public/js/buildings/1v1.js @@ -5,41 +5,48 @@ ═══════════════════════════════════════════════════════════════ */ /* ── Konfiguration aus EJS-Bridge ────────────────────────── */ -const matchId = new URLSearchParams(window.location.search).get('match') || window.GAME_CONFIG.matchId; -const mySlot = new URLSearchParams(window.location.search).get('slot') || window.GAME_CONFIG.mySlot; -const amIPlayer1 = mySlot === 'player1'; +const matchId = + new URLSearchParams(window.location.search).get("match") || + window.GAME_CONFIG.matchId; +const mySlot = + new URLSearchParams(window.location.search).get("slot") || + window.GAME_CONFIG.mySlot; +const amIPlayer1 = mySlot === "player1"; /* ── Gegner-Name aus URL setzen ──────────────────────────── */ const _urlParams = new URLSearchParams(window.location.search); -const opponentNameFromUrl = _urlParams.get('opponent'); +const opponentNameFromUrl = _urlParams.get("opponent"); if (opponentNameFromUrl) { - const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); + const oppEl = document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft"); if (oppEl) oppEl.textContent = decodeURIComponent(opponentNameFromUrl); } /* ═══════════════════════════════════════════════════════════ SPIELFELD AUFBAUEN ═══════════════════════════════════════════════════════════ */ -['row1', 'row2'].forEach(rowId => { +["row1", "row2"].forEach((rowId) => { const row = document.getElementById(rowId); for (let i = 1; i <= 11; i++) { - const s = document.createElement('div'); - s.className = 'card-slot'; - s.dataset.row = rowId; + const s = document.createElement("div"); + s.className = "card-slot"; + s.dataset.row = rowId; s.dataset.slotIndex = i; - s.id = `${rowId}-slot-${i}`; - s.innerHTML = '\u2736' + i + ''; + s.id = `${rowId}-slot-${i}`; + s.innerHTML = + '\u2736' + + i + + ""; row.appendChild(s); } }); -const hand = document.getElementById('handArea'); +const hand = document.getElementById("handArea"); /* ── Deck-Stapel ─────────────────────────────────────────── */ -const deckSlot = document.createElement('div'); -deckSlot.className = 'hand-slot hand-slot-deck'; -deckSlot.id = 'deck-stack'; -deckSlot.title = 'Dein Deck'; +const deckSlot = document.createElement("div"); +deckSlot.className = "hand-slot hand-slot-deck"; +deckSlot.id = "deck-stack"; +deckSlot.title = "Dein Deck"; deckSlot.innerHTML = `
@@ -51,11 +58,19 @@ deckSlot.innerHTML = ` hand.appendChild(deckSlot); /* ── Hand-Karten-Slots ───────────────────────────────────── */ -const handCardIds = ['hand-card-1','hand-card-2','hand-card-3','hand-card-4','hand-card-5','hand-card-6','hand-card-7']; -handCardIds.forEach(id => { - const s = document.createElement('div'); - s.className = 'hand-slot hand-slot-card'; - s.id = id; +const handCardIds = [ + "hand-card-1", + "hand-card-2", + "hand-card-3", + "hand-card-4", + "hand-card-5", + "hand-card-6", + "hand-card-7", +]; +handCardIds.forEach((id) => { + const s = document.createElement("div"); + s.className = "hand-slot hand-slot-card"; + s.id = id; s.innerHTML = '\uD83C\uDCCF'; hand.appendChild(s); }); @@ -65,24 +80,26 @@ handCardIds.forEach(id => { ═══════════════════════════════════════════════════════════ */ let deckQueue = []; const handSlotState = {}; -handCardIds.forEach(id => { handSlotState[id] = null; }); +handCardIds.forEach((id) => { + handSlotState[id] = null; +}); /* ── SVG-Icons ───────────────────────────────────────────── */ const SVG_RANGE = ``; -const SVG_RACE = ``; +const SVG_RACE = ``; /* ── Hand-Slot rendern ───────────────────────────────────── */ function renderHandSlot(id) { - const slot = document.getElementById(id); + const slot = document.getElementById(id); const state = handSlotState[id]; if (!slot) return; - slot.innerHTML = ''; - slot.style.backgroundImage = 'none'; - slot.style.opacity = '1'; - slot.style.filter = 'none'; - slot.style.backgroundColor = '#0a0805'; - slot.classList.remove('hand-slot-ready', 'hand-slot--filled'); + slot.innerHTML = ""; + slot.style.backgroundImage = "none"; + slot.style.opacity = "1"; + slot.style.filter = "none"; + slot.style.backgroundColor = "#0a0805"; + slot.classList.remove("hand-slot-ready", "hand-slot--filled"); if (!state) { slot.innerHTML = '\uD83C\uDCCF'; @@ -94,20 +111,23 @@ function renderHandSlot(id) { const { card, currentCd } = state; const isReady = currentCd <= 0; - const atkVal = card.attack ?? null; + const atkVal = card.attack ?? null; const defVal = card.defends ?? null; - const rngVal = card.range ?? null; - const rceVal = card.race ?? null; + const rngVal = card.range ?? null; + const rceVal = card.race ?? null; const statsHtml = `
- ${atkVal != null ? `${atkVal}` : ''} - ${defVal != null ? `${defVal}` : ''} - ${card.cooldown != null ? `${isReady ? '\u2713' : currentCd}` : ''} - ${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ''} - ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ''} + ${atkVal != null ? `${atkVal}` : ""} + ${defVal != null ? `${defVal}` : ""} + ${card.cooldown != null ? `${isReady ? "\u2713" : currentCd}` : ""} + ${rngVal != null ? `${SVG_RANGE} ${rngVal}` : ""} + ${rceVal != null ? `${SVG_RACE} ${rceVal}` : ""}
`; - const readyBadge = (isReady && isMyTurn) ? '
SPIELEN
' : ''; + const readyBadge = + isReady && isMyTurn + ? '
SPIELEN
' + : ""; slot.innerHTML = card.image ? `${statsHtml}${readyBadge}` @@ -116,8 +136,8 @@ function renderHandSlot(id) { ${card.name}
${statsHtml}${readyBadge}`; - slot.classList.toggle('hand-slot-ready', isReady); - slot.classList.add('hand-slot--filled'); + slot.classList.toggle("hand-slot-ready", isReady); + slot.classList.add("hand-slot--filled"); if (isReady && isMyTurn) { slot.draggable = true; @@ -129,27 +149,31 @@ function renderHandSlot(id) { } function setHandSlot(id, card) { - handSlotState[id] = { card, currentCd: card.cooldown ?? 0, wasReduced: false }; + handSlotState[id] = { + card, + currentCd: card.cooldown ?? 0, + wasReduced: false, + }; renderHandSlot(id); } function drawNextCard() { if (deckQueue.length === 0) return; - const freeSlot = handCardIds.find(id => handSlotState[id] === null); + const freeSlot = handCardIds.find((id) => handSlotState[id] === null); if (!freeSlot) return; const card = deckQueue.shift(); setHandSlot(freeSlot, card); - const countEl = document.getElementById('deck-count'); + const countEl = document.getElementById("deck-count"); if (countEl) countEl.textContent = deckQueue.length; } function tickHandCooldowns() { - handCardIds.forEach(id => { + handCardIds.forEach((id) => { const state = handSlotState[id]; if (!state) return; const baseCd = Number(state.card.cooldown || 0); if (state.currentCd <= 0 && !state.wasReduced) { - state.currentCd = baseCd > 0 ? Math.max(1, Math.floor(baseCd / 2)) : 0; + state.currentCd = baseCd > 0 ? Math.max(1, Math.floor(baseCd / 2)) : 0; state.wasReduced = true; } else if (state.currentCd > 0) { state.currentCd--; @@ -161,43 +185,48 @@ function tickHandCooldowns() { /* ── Deck laden ──────────────────────────────────────────── */ (async () => { try { - const urlP = new URLSearchParams(window.location.search); - const deckId = urlP.get('deck') || sessionStorage.getItem('selectedDeckId'); + const urlP = new URLSearchParams(window.location.search); + const deckId = urlP.get("deck") || sessionStorage.getItem("selectedDeckId"); if (!deckId) return; - const res = await fetch('/api/decks/' + deckId + '/cards'); + const res = await fetch("/api/decks/" + deckId + "/cards"); if (!res.ok) return; const cards = await res.json(); const expanded = []; - cards.forEach(c => { for (let i = 0; i < (c.amount ?? 1); i++) expanded.push(c); }); + cards.forEach((c) => { + for (let i = 0; i < (c.amount ?? 1); i++) expanded.push(c); + }); for (let i = expanded.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [expanded[i], expanded[j]] = [expanded[j], expanded[i]]; } - for (let i = 0; i < 3; i++) { if (expanded[i]) setHandSlot(handCardIds[i], expanded[i]); } + for (let i = 0; i < 3; i++) { + if (expanded[i]) setHandSlot(handCardIds[i], expanded[i]); + } deckQueue = expanded.slice(3); - const countEl = document.getElementById('deck-count'); + const countEl = document.getElementById("deck-count"); if (countEl) countEl.textContent = deckQueue.length; - } catch (e) { console.error('[Battlefield] Deck laden:', e); } + } catch (e) { + console.error("[Battlefield] Deck laden:", e); + } })(); - /* ═══════════════════════════════════════════════════════════ ZUG-TIMER ═══════════════════════════════════════════════════════════ */ const TURN_SECONDS = 30; -const TT_CIRCUM = 2 * Math.PI * 18; -let turnTimerInt = null; -let turnSecsLeft = TURN_SECONDS; -let amILeftPlayer = null; -let isMyTurn = false; +const TT_CIRCUM = 2 * Math.PI * 18; +let turnTimerInt = null; +let turnSecsLeft = TURN_SECONDS; +let amILeftPlayer = null; +let isMyTurn = false; function startTurnTimer(activeName) { clearInterval(turnTimerInt); turnSecsLeft = TURN_SECONDS; updateTimerUI(turnSecsLeft, activeName); - const wrap = document.getElementById('turn-timer-wrap'); - if (wrap) wrap.style.display = 'flex'; + const wrap = document.getElementById("turn-timer-wrap"); + if (wrap) wrap.style.display = "flex"; turnTimerInt = setInterval(() => { turnSecsLeft--; updateTimerUI(turnSecsLeft, activeName); @@ -210,21 +239,27 @@ function startTurnTimer(activeName) { function stopTurnTimer() { clearInterval(turnTimerInt); - const wrap = document.getElementById('turn-timer-wrap'); - if (wrap) wrap.style.display = 'none'; + const wrap = document.getElementById("turn-timer-wrap"); + if (wrap) wrap.style.display = "none"; } function updateTimerUI(secs, activeName) { - const num = document.getElementById('turn-timer-num'); - const circle = document.getElementById('tt-circle'); - const wrap = document.getElementById('turn-timer-wrap'); - if (num) num.textContent = secs; - if (wrap && activeName) wrap.title = activeName + ' ist am Zug'; + const num = document.getElementById("turn-timer-num"); + const circle = document.getElementById("tt-circle"); + const wrap = document.getElementById("turn-timer-wrap"); + if (num) num.textContent = secs; + if (wrap && activeName) wrap.title = activeName + " ist am Zug"; if (circle) { circle.style.strokeDashoffset = TT_CIRCUM * (1 - secs / TURN_SECONDS); - circle.style.stroke = secs > 15 ? '#27ae60' : secs > 8 ? '#f39c12' : '#e74c3c'; - if (secs <= 8) { num.style.color = '#e74c3c'; num.style.animation = 'tt-pulse 0.5s ease-in-out infinite'; } - else { num.style.color = '#fff'; num.style.animation = 'none'; } + circle.style.stroke = + secs > 15 ? "#27ae60" : secs > 8 ? "#f39c12" : "#e74c3c"; + if (secs <= 8) { + num.style.color = "#e74c3c"; + num.style.animation = "tt-pulse 0.5s ease-in-out infinite"; + } else { + num.style.color = "#fff"; + num.style.animation = "none"; + } } } @@ -233,27 +268,27 @@ function updateTimerUI(secs, activeName) { ═══════════════════════════════════════════════════════════ */ function setTurnState(myTurn, activeName) { isMyTurn = myTurn; - const btn = document.getElementById('end-turn-btn'); + const btn = document.getElementById("end-turn-btn"); if (!btn) return; - handCardIds.forEach(id => renderHandSlot(id)); + handCardIds.forEach((id) => renderHandSlot(id)); if (myTurn) { - btn.disabled = false; - btn.textContent = 'Zug beenden'; - btn.style.opacity = '1'; - document.getElementById('turn-indicator')?.remove(); - startTurnTimer(activeName || 'Du'); + btn.disabled = false; + btn.textContent = "Zug beenden"; + btn.style.opacity = "1"; + document.getElementById("turn-indicator")?.remove(); + startTurnTimer(activeName || "Du"); } else { - btn.disabled = true; - btn.style.opacity = '0.4'; + btn.disabled = true; + btn.style.opacity = "0.4"; showTurnIndicator(activeName); - startTurnTimer(activeName || 'Gegner'); + startTurnTimer(activeName || "Gegner"); } } function showTurnIndicator(activeName) { - document.getElementById('turn-indicator')?.remove(); - const ind = document.createElement('div'); - ind.id = 'turn-indicator'; + document.getElementById("turn-indicator")?.remove(); + const ind = document.createElement("div"); + ind.id = "turn-indicator"; ind.style.cssText = ` position:fixed;bottom:calc(var(--s)*200);left:50%;transform:translateX(-50%); background:linear-gradient(135deg,rgba(20,20,40,0.95),rgba(10,10,25,0.95)); @@ -262,7 +297,9 @@ function showTurnIndicator(activeName) { font-size:calc(var(--s)*11);letter-spacing:calc(var(--s)*3); padding:calc(var(--s)*8) calc(var(--s)*20);z-index:100;pointer-events:none; text-transform:uppercase;box-shadow:0 4px 20px rgba(0,0,0,0.6);`; - ind.textContent = activeName ? `\u23F3 ${activeName} ist am Zug` : '\u23F3 Gegner ist am Zug'; + ind.textContent = activeName + ? `\u23F3 ${activeName} ist am Zug` + : "\u23F3 Gegner ist am Zug"; document.body.appendChild(ind); } @@ -273,10 +310,10 @@ function endMyTurn() { setTurnState(false); tickHandCooldowns(); drawNextCard(); - socket.emit('end_turn', { matchId, slot: mySlot }); + socket.emit("end_turn", { matchId, slot: mySlot }); } -document.getElementById('end-turn-btn')?.addEventListener('click', endMyTurn); +document.getElementById("end-turn-btn")?.addEventListener("click", endMyTurn); /* ═══════════════════════════════════════════════════════════ KARTE AUF BOARD RENDERN @@ -290,8 +327,8 @@ document.getElementById('end-turn-btn')?.addEventListener('click', endMyTurn); const boardState = {}; /* ── Kampf + HP Animationen ────────────────────────────── */ -(function() { - const s = document.createElement('style'); +(function () { + const s = document.createElement("style"); s.textContent = ` @keyframes dmg-float { 0% { opacity:1; transform:translateX(-50%) translateY(0); } @@ -308,34 +345,36 @@ const boardState = {}; /* ── Spieler-Farbe auf Slot oder Avatar anwenden ────────────────── */ function applyOwnerStyle(el, owner) { if (!el || !owner) return; - const isLeft = owner === (window._leftSlot || 'player1'); + const isLeft = owner === (window._leftSlot || "player1"); if (isLeft) { - el.style.borderColor = 'rgba(60, 140, 255, 0.95)'; - el.style.boxShadow = '0 0 14px rgba(60, 140, 255, 0.5), inset 0 0 10px rgba(60,140,255,0.08)'; + el.style.borderColor = "rgba(60, 140, 255, 0.95)"; + el.style.boxShadow = + "0 0 14px rgba(60, 140, 255, 0.5), inset 0 0 10px rgba(60,140,255,0.08)"; } else { - el.style.borderColor = 'rgba(220, 60, 60, 0.95)'; - el.style.boxShadow = '0 0 14px rgba(220, 60, 60, 0.5), inset 0 0 10px rgba(220,60,60,0.08)'; + el.style.borderColor = "rgba(220, 60, 60, 0.95)"; + el.style.boxShadow = + "0 0 14px rgba(220, 60, 60, 0.5), inset 0 0 10px rgba(220,60,60,0.08)"; } } function buildStatsHtml(card) { - const atk = card.attack ?? null; + const atk = card.attack ?? null; const def = card.defends ?? null; - const cd = card.cooldown ?? null; - const rng = card.range ?? null; - const rce = card.race ?? null; + const cd = card.cooldown ?? null; + const rng = card.range ?? null; + const rce = card.race ?? null; return `
- ${atk != null ? `${atk}` : ''} - ${def != null ? `${def}` : ''} - ${cd != null ? `${cd}` : ''} - ${rng != null ? `${SVG_RANGE} ${rng}` : ''} - ${rce != null ? `${SVG_RACE} ${rce}` : ''} + ${atk != null ? `${atk}` : ""} + ${def != null ? `${def}` : ""} + ${cd != null ? `${cd}` : ""} + ${rng != null ? `${SVG_RANGE} ${rng}` : ""} + ${rce != null ? `${SVG_RACE} ${rce}` : ""}
`; } function renderCardOnBoard(slotEl, card, owner) { const statsHtml = buildStatsHtml(card); - slotEl.classList.add('slot-occupied'); + slotEl.classList.add("slot-occupied"); applyOwnerStyle(slotEl, owner); slotEl.innerHTML = card.image ? `${statsHtml}` @@ -344,7 +383,7 @@ function renderCardOnBoard(slotEl, card, owner) { function renderCardInSlot(slot, card) { if (!slot || !card) return; - slot.classList.add('slot-occupied'); + slot.classList.add("slot-occupied"); slot.innerHTML = card.image ? `${buildStatsHtml(card)}` : `
\u2694\uFE0F${card.name}
${buildStatsHtml(card)}`; @@ -355,13 +394,15 @@ function clearBoardSlot(slotId) { delete boardState[slotId]; const el = document.getElementById(slotId); if (!el) return; - el.classList.remove('slot-occupied'); - el.style.borderColor = ''; - el.style.boxShadow = ''; - el.innerHTML = '\u2736' + slotId.split('-slot-')[1] + ''; + el.classList.remove("slot-occupied"); + el.style.borderColor = ""; + el.style.boxShadow = ""; + el.innerHTML = + '\u2736' + + slotId.split("-slot-")[1] + + ""; } - /* ═══════════════════════════════════════════════════════════ SOCKET & ARENA-JOIN ═══════════════════════════════════════════════════════════ */ @@ -369,37 +410,60 @@ const socket = io(); let myIngameName = null; function emitArenaJoin(me) { - myIngameName = (me && (me.ingame_name || me.username || me.name || String(me.id))) || 'Spieler'; - const myNameEl = document.getElementById(amIPlayer1 ? 'nameLeft' : 'nameRight'); + myIngameName = + (me && (me.ingame_name || me.username || me.name || String(me.id))) || + "Spieler"; + const myNameEl = document.getElementById( + amIPlayer1 ? "nameLeft" : "nameRight", + ); if (myNameEl) myNameEl.textContent = myIngameName; - console.log('[1v1] emitArenaJoin:', { matchId, slot: mySlot, name: myIngameName }); - socket.emit('arena_join', { matchId, slot: mySlot, accountId: me?.id ?? null, playerName: myIngameName }); + console.log("[1v1] emitArenaJoin:", { + matchId, + slot: mySlot, + name: myIngameName, + }); + socket.emit("arena_join", { + matchId, + slot: mySlot, + accountId: me?.id ?? null, + playerName: myIngameName, + }); } function fetchAndJoin() { - fetch('/arena/me') - .then(r => r.json()) - .then(me => emitArenaJoin(me)) - .catch(() => { console.warn('[1v1] /arena/me fehlgeschlagen'); emitArenaJoin(null); }); + fetch("/arena/me") + .then((r) => r.json()) + .then((me) => emitArenaJoin(me)) + .catch(() => { + console.warn("[1v1] /arena/me fehlgeschlagen"); + emitArenaJoin(null); + }); } fetchAndJoin(); -socket.on('connect', () => { - console.log('[1v1] Socket connected:', socket.id); - if (myIngameName && myIngameName !== 'Spieler') { - socket.emit('arena_join', { matchId, slot: mySlot, accountId: null, playerName: myIngameName }); +socket.on("connect", () => { + console.log("[1v1] Socket connected:", socket.id); + if (myIngameName && myIngameName !== "Spieler") { + socket.emit("arena_join", { + matchId, + slot: mySlot, + accountId: null, + playerName: myIngameName, + }); } else { fetchAndJoin(); } }); -socket.on('connect_error', err => console.error('[1v1] connect_error:', err.message)); +socket.on("connect_error", (err) => + console.error("[1v1] connect_error:", err.message), +); const readyFallbackTimer = setTimeout(() => { - console.warn('[1v1] Kein arena-Event nach 10s'); - document.getElementById('connecting-overlay')?.remove(); - const lo = document.getElementById('board-lock-overlay'); - if (lo) lo.style.display = 'flex'; + console.warn("[1v1] Kein arena-Event nach 10s"); + document.getElementById("connecting-overlay")?.remove(); + const lo = document.getElementById("board-lock-overlay"); + if (lo) lo.style.display = "flex"; }, 10000); /* ── Board-Sync ──────────────────────────────────────────── */ @@ -409,66 +473,81 @@ const readyFallbackTimer = setTimeout(() => { */ function applyBoardSync(cards) { if (!cards || !cards.length) return; - cards.forEach(cd => { + cards.forEach((cd) => { const slotEl = document.getElementById(cd.boardSlot); if (!slotEl || boardState[cd.boardSlot]) return; - const owner = cd.owner ?? (cd.slot ?? 'unknown'); + const owner = cd.owner ?? cd.slot ?? "unknown"; boardState[cd.boardSlot] = { card: cd.card, owner }; renderCardOnBoard(slotEl, cd.card, cd.owner); }); - console.log('[1v1] Board sync:', cards.length, 'Karten'); + console.log("[1v1] Board sync:", cards.length, "Karten"); } /* ── Socket Events ───────────────────────────────────────── */ -socket.on('arena_opponent_joined', data => { - console.log('[Arena] arena_opponent_joined:', data); +socket.on("arena_opponent_joined", (data) => { + console.log("[Arena] arena_opponent_joined:", data); clearTimeout(readyFallbackTimer); - document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft').textContent = data.name || 'Gegner'; - document.getElementById('connecting-overlay')?.remove(); - const lo = document.getElementById('board-lock-overlay'); - if (lo) lo.style.display = 'flex'; + document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft").textContent = + data.name || "Gegner"; + document.getElementById("connecting-overlay")?.remove(); + const lo = document.getElementById("board-lock-overlay"); + if (lo) lo.style.display = "flex"; }); -socket.on('arena_ready', data => { - console.log('[Arena] arena_ready:', data); +socket.on("arena_ready", (data) => { + console.log("[Arena] arena_ready:", data); clearTimeout(readyFallbackTimer); - document.getElementById('connecting-overlay')?.remove(); + document.getElementById("connecting-overlay")?.remove(); if (data.boardSync) applyBoardSync(data.boardSync); if (data.hp && data.maxHp) applyHpFromEvent(data); const oppName = amIPlayer1 ? data.player2 : data.player1; - const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); - const urlOppName = _urlParams.get('opponent'); - if (oppEl && oppName && !['Spieler','Spieler 1','Spieler 2','Gegner'].includes(oppName)) { + const oppEl = document.getElementById(amIPlayer1 ? "nameRight" : "nameLeft"); + const urlOppName = _urlParams.get("opponent"); + if ( + oppEl && + oppName && + !["Spieler", "Spieler 1", "Spieler 2", "Gegner"].includes(oppName) + ) { oppEl.textContent = oppName; - } else if (oppEl && urlOppName && (!oppEl.textContent || oppEl.textContent === 'Gegner')) { + } else if ( + oppEl && + urlOppName && + (!oppEl.textContent || oppEl.textContent === "Gegner") + ) { oppEl.textContent = decodeURIComponent(urlOppName); } - const myEl = document.getElementById(amIPlayer1 ? 'nameLeft' : 'nameRight'); + const myEl = document.getElementById(amIPlayer1 ? "nameLeft" : "nameRight"); if (myEl && myIngameName) myEl.textContent = myIngameName; - const lo = document.getElementById('board-lock-overlay'); - if (lo) lo.style.display = 'flex'; + const lo = document.getElementById("board-lock-overlay"); + if (lo) lo.style.display = "flex"; }); -socket.on('board_sync', data => { if (data.cards) applyBoardSync(data.cards); }); +socket.on("board_sync", (data) => { + if (data.cards) applyBoardSync(data.cards); +}); -socket.on('turn_change', data => { - document.getElementById('board-lock-overlay')?.remove(); - document.getElementById('connecting-overlay')?.remove(); +socket.on("turn_change", (data) => { + document.getElementById("board-lock-overlay")?.remove(); + document.getElementById("connecting-overlay")?.remove(); if (data.boardSync) applyBoardSync(data.boardSync); - const activeSlot = data.activeSlot; - const nowMyTurn = activeSlot === mySlot; - const activeNameEl = document.getElementById(activeSlot === (window._leftSlot || 'player1') ? 'nameLeft' : 'nameRight'); - const activeName = activeNameEl?.textContent || (nowMyTurn ? 'Du' : 'Gegner'); + const activeSlot = data.activeSlot; + const nowMyTurn = activeSlot === mySlot; + const activeNameEl = document.getElementById( + activeSlot === (window._leftSlot || "player1") ? "nameLeft" : "nameRight", + ); + const activeName = activeNameEl?.textContent || (nowMyTurn ? "Du" : "Gegner"); console.log(`[1v1] turn_change: ${activeSlot} | meinZug: ${nowMyTurn}`); setTurnState(nowMyTurn, activeName); if (data.hp && data.maxHp) applyHpFromEvent(data); }); -socket.on('turn_started', data => { - const myT = data.slot === mySlot; - const nameEl = document.getElementById(data.slot === (window._leftSlot || 'player1') ? 'nameLeft' : 'nameRight'); - setTurnState(myT, nameEl?.textContent || (myT ? 'Du' : 'Gegner')); +socket.on("turn_started", (data) => { + const myT = data.slot === mySlot; + const nameEl = document.getElementById( + data.slot === (window._leftSlot || "player1") ? "nameLeft" : "nameRight", + ); + setTurnState(myT, nameEl?.textContent || (myT ? "Du" : "Gegner")); }); /* ═══════════════════════════════════════════════════════════ @@ -479,80 +558,95 @@ let myReady = false; function handleBereit() { if (myReady) return; myReady = true; - const btn = document.getElementById('bereit-btn'); - btn.textContent = '\u2714 BEREIT'; - btn.classList.add('bereit-clicked'); + const btn = document.getElementById("bereit-btn"); + btn.textContent = "\u2714 BEREIT"; + btn.classList.add("bereit-clicked"); btn.disabled = true; - socket.emit('player_ready', { matchId, slot: mySlot }); + socket.emit("player_ready", { matchId, slot: mySlot }); } const CIRCUMFERENCE = 2 * Math.PI * 34; -const timerCircle = document.getElementById('timer-circle'); +const timerCircle = document.getElementById("timer-circle"); if (timerCircle) timerCircle.style.strokeDasharray = CIRCUMFERENCE; -socket.on('ready_timer', data => { - const num = document.getElementById('ready-timer-number'); +socket.on("ready_timer", (data) => { + const num = document.getElementById("ready-timer-number"); if (num) num.textContent = data.remaining; if (timerCircle) { - timerCircle.style.strokeDashoffset = CIRCUMFERENCE * (1 - data.remaining / 30); - timerCircle.style.stroke = data.remaining > 15 ? '#27ae60' : data.remaining > 7 ? '#f39c12' : '#e74c3c'; + timerCircle.style.strokeDashoffset = + CIRCUMFERENCE * (1 - data.remaining / 30); + timerCircle.style.stroke = + data.remaining > 15 + ? "#27ae60" + : data.remaining > 7 + ? "#f39c12" + : "#e74c3c"; } }); -socket.on('ready_status', data => { - console.log('[1v1] ready_status:', data); - const pip1 = document.getElementById('pip-player1'); - const pip2 = document.getElementById('pip-player2'); +socket.on("ready_status", (data) => { + console.log("[1v1] ready_status:", data); + const pip1 = document.getElementById("pip-player1"); + const pip2 = document.getElementById("pip-player2"); if (data.readySlots) { - if (data.readySlots.includes('player1') && pip1) - pip1.textContent = '\u2705 ' + (document.getElementById('nameLeft')?.textContent || 'Spieler 1'); - if (data.readySlots.includes('player2') && pip2) - pip2.textContent = '\u2705 ' + (document.getElementById('nameRight')?.textContent || 'Spieler 2'); + if (data.readySlots.includes("player1") && pip1) + pip1.textContent = + "\u2705 " + + (document.getElementById("nameLeft")?.textContent || "Spieler 1"); + if (data.readySlots.includes("player2") && pip2) + pip2.textContent = + "\u2705 " + + (document.getElementById("nameRight")?.textContent || "Spieler 2"); } if (data.readyCount === 2) { - const seed = matchId.split('').reduce((a, c) => a + c.charCodeAt(0), 0); - const flip = seed % 2 === 1; - const myName = myIngameName || 'Spieler'; - const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft'); - const oppName = oppEl?.textContent || 'Gegner'; - const p1Name = amIPlayer1 ? myName : oppName; - const p2Name = amIPlayer1 ? oppName : myName; - const leftName = flip ? p2Name : p1Name; + const seed = matchId.split("").reduce((a, c) => a + c.charCodeAt(0), 0); + const flip = seed % 2 === 1; + const myName = myIngameName || "Spieler"; + const oppEl = document.getElementById( + amIPlayer1 ? "nameRight" : "nameLeft", + ); + const oppName = oppEl?.textContent || "Gegner"; + const p1Name = amIPlayer1 ? myName : oppName; + const p2Name = amIPlayer1 ? oppName : myName; + const leftName = flip ? p2Name : p1Name; const rightName = flip ? p1Name : p2Name; - document.getElementById('nameLeft').textContent = leftName; - document.getElementById('nameRight').textContent = rightName; + document.getElementById("nameLeft").textContent = leftName; + document.getElementById("nameRight").textContent = rightName; - ['avLeft', 'avRight'].forEach(avId => { - const av = document.getElementById(avId); - const ph = av?.querySelector('.av-placeholder'); - const name = avId === 'avLeft' ? leftName : rightName; - if (ph) ph.innerHTML = `
${name}
`; + ["avLeft", "avRight"].forEach((avId) => { + const av = document.getElementById(avId); + const ph = av?.querySelector(".av-placeholder"); + const name = avId === "avLeft" ? leftName : rightName; + if (ph) + ph.innerHTML = `
${name}
`; }); - document.getElementById('board-lock-overlay')?.remove(); - amILeftPlayer = flip ? mySlot === 'player2' : mySlot === 'player1'; + document.getElementById("board-lock-overlay")?.remove(); + amILeftPlayer = flip ? mySlot === "player2" : mySlot === "player1"; // Avatare farbig umranden: links=blau, rechts=rot - const _avL = document.getElementById('avLeft'); - const _avR = document.getElementById('avRight'); + const _avL = document.getElementById("avLeft"); + const _avR = document.getElementById("avRight"); if (_avL) { - _avL.style.borderColor = 'rgba(60, 140, 255, 0.95)'; - _avL.style.boxShadow = '0 0 28px rgba(60, 140, 255, 0.55), inset 0 0 20px rgba(0,0,0,0.5)'; + _avL.style.borderColor = "rgba(60, 140, 255, 0.95)"; + _avL.style.boxShadow = + "0 0 28px rgba(60, 140, 255, 0.55), inset 0 0 20px rgba(0,0,0,0.5)"; } if (_avR) { - _avR.style.borderColor = 'rgba(220, 60, 60, 0.95)'; - _avR.style.boxShadow = '0 0 28px rgba(220, 60, 60, 0.55), inset 0 0 20px rgba(0,0,0,0.5)'; + _avR.style.borderColor = "rgba(220, 60, 60, 0.95)"; + _avR.style.boxShadow = + "0 0 28px rgba(220, 60, 60, 0.55), inset 0 0 20px rgba(0,0,0,0.5)"; } - const board = document.querySelector('.board'); + const board = document.querySelector(".board"); if (board) { - board.classList.remove('my-side-left', 'my-side-right'); - board.classList.add(amILeftPlayer ? 'my-side-left' : 'my-side-right'); + board.classList.remove("my-side-left", "my-side-right"); + board.classList.add(amILeftPlayer ? "my-side-left" : "my-side-right"); } - const leftSlot = flip ? 'player2' : 'player1'; + const leftSlot = flip ? "player2" : "player1"; window._leftSlot = leftSlot; console.log(`[1v1] linker Slot: ${leftSlot} | ich: ${mySlot}`); @@ -562,76 +656,80 @@ socket.on('ready_status', data => { const el = document.getElementById(slotId); if (el && entry?.owner) applyOwnerStyle(el, entry.owner); }); - if (mySlot === 'player1') { - socket.emit('start_turn_request', { matchId, starterSlot: leftSlot }); + if (mySlot === "player1") { + socket.emit("start_turn_request", { matchId, starterSlot: leftSlot }); } } }); - /* ═══════════════════════════════════════════════════════════ DRAG & DROP ═══════════════════════════════════════════════════════════ */ function isMyZone(slotIndex) { - const idx = Number(slotIndex); + const idx = Number(slotIndex); const iAmLeft = amILeftPlayer !== null ? amILeftPlayer : amIPlayer1; return iAmLeft ? idx <= 3 : idx >= 9; } function setDropZones(active) { - document.querySelectorAll('.card-slot').forEach(slot => { + document.querySelectorAll(".card-slot").forEach((slot) => { const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx)) return; - if (active && !boardState[slot.id]) slot.classList.add('drop-zone-active'); - else slot.classList.remove('drop-zone-active', 'drop-zone-hover'); + if (active && !boardState[slot.id]) slot.classList.add("drop-zone-active"); + else slot.classList.remove("drop-zone-active", "drop-zone-hover"); }); } let draggedCardSlotId = null; -document.getElementById('handArea').addEventListener('dragstart', e => { - const slot = e.target.closest('[data-card-slot-id]'); - if (!slot || !isMyTurn) { e.preventDefault(); return; } +document.getElementById("handArea").addEventListener("dragstart", (e) => { + const slot = e.target.closest("[data-card-slot-id]"); + if (!slot || !isMyTurn) { + e.preventDefault(); + return; + } draggedCardSlotId = slot.dataset.cardSlotId; - slot.classList.add('dragging'); - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.setData('text/plain', draggedCardSlotId); + slot.classList.add("dragging"); + e.dataTransfer.effectAllowed = "move"; + e.dataTransfer.setData("text/plain", draggedCardSlotId); setTimeout(() => setDropZones(true), 0); }); -document.getElementById('handArea').addEventListener('dragend', e => { - const slot = e.target.closest('[data-card-slot-id]'); - if (slot) slot.classList.remove('dragging'); +document.getElementById("handArea").addEventListener("dragend", (e) => { + const slot = e.target.closest("[data-card-slot-id]"); + if (slot) slot.classList.remove("dragging"); setDropZones(false); draggedCardSlotId = null; }); -['row1', 'row2'].forEach(rowId => { +["row1", "row2"].forEach((rowId) => { const row = document.getElementById(rowId); - row.addEventListener('dragover', e => { - const slot = e.target.closest('.card-slot'); + row.addEventListener("dragover", (e) => { + const slot = e.target.closest(".card-slot"); if (!slot) return; const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx) || boardState[slot.id]) return; e.preventDefault(); - e.dataTransfer.dropEffect = 'move'; - row.querySelectorAll('.drop-zone-hover').forEach(s => s.classList.remove('drop-zone-hover')); - slot.classList.add('drop-zone-hover'); + e.dataTransfer.dropEffect = "move"; + row + .querySelectorAll(".drop-zone-hover") + .forEach((s) => s.classList.remove("drop-zone-hover")); + slot.classList.add("drop-zone-hover"); }); - row.addEventListener('dragleave', e => { - const slot = e.target.closest('.card-slot'); - if (slot) slot.classList.remove('drop-zone-hover'); + row.addEventListener("dragleave", (e) => { + const slot = e.target.closest(".card-slot"); + if (slot) slot.classList.remove("drop-zone-hover"); }); - row.addEventListener('drop', e => { + row.addEventListener("drop", (e) => { e.preventDefault(); - const slot = e.target.closest('.card-slot'); + const slot = e.target.closest(".card-slot"); if (!slot) return; const idx = Number(slot.dataset.slotIndex); if (!isMyZone(idx) || boardState[slot.id]) return; - const sourceId = draggedCardSlotId || e.dataTransfer.getData('text/plain'); + const sourceId = draggedCardSlotId || e.dataTransfer.getData("text/plain"); if (!sourceId) return; const cardState = handSlotState[sourceId]; if (!cardState || cardState.currentCd > 0) return; @@ -642,29 +740,35 @@ document.getElementById('handArea').addEventListener('dragend', e => { /* ── boardState mit owner speichern ─────────────────── */ boardState[slot.id] = { card: cardState.card, owner: mySlot }; renderCardOnBoard(slot, cardState.card, mySlot); - slot.classList.remove('drop-zone-active', 'drop-zone-hover'); + slot.classList.remove("drop-zone-active", "drop-zone-hover"); - socket.emit('card_played', { - matchId, slot: mySlot, boardSlot: slot.id, - row: slot.dataset.row, slotIndex: idx, card: cardState.card, + socket.emit("card_played", { + matchId, + slot: mySlot, + boardSlot: slot.id, + row: slot.dataset.row, + slotIndex: idx, + card: cardState.card, }); console.log(`[1v1] Karte gespielt: ${cardState.card.name} -> ${slot.id}`); }); }); -socket.on('card_played', data => { +socket.on("card_played", (data) => { if (data.slot === mySlot) return; if (boardState[data.boardSlot]) return; const slotEl = document.getElementById(data.boardSlot); - if (!slotEl) { console.warn('[1v1] card_played: Slot fehlt:', data.boardSlot); return; } + if (!slotEl) { + console.warn("[1v1] card_played: Slot fehlt:", data.boardSlot); + return; + } /* ── boardState mit owner des Gegners speichern ─────── */ boardState[data.boardSlot] = { card: data.card, owner: data.slot }; renderCardOnBoard(slotEl, data.card, data.slot); - console.log('[1v1] Gegner Karte:', data.card?.name, '->', data.boardSlot); + console.log("[1v1] Gegner Karte:", data.card?.name, "->", data.boardSlot); }); - /* ═══════════════════════════════════════════════════════════ KAMPFPHASE – CLIENT-SEITIGE VERARBEITUNG Der Server sendet nach end_turn ein 'combat_phase'-Event mit: @@ -672,16 +776,16 @@ socket.on('card_played', data => { ═══════════════════════════════════════════════════════════ */ /* Timing (ms) zwischen einzelnen Kampf-Events */ -const COMBAT_DELAY_MOVE = 350; +const COMBAT_DELAY_MOVE = 350; const COMBAT_DELAY_ATTACK = 450; -const COMBAT_DELAY_DIE = 300; +const COMBAT_DELAY_DIE = 300; /* Kampf-Log-Banner (erscheint kurz über dem Board) */ -function showCombatBanner(text, color = '#f0d060') { - const old = document.getElementById('combat-banner'); +function showCombatBanner(text, color = "#f0d060") { + const old = document.getElementById("combat-banner"); if (old) old.remove(); - const el = document.createElement('div'); - el.id = 'combat-banner'; + const el = document.createElement("div"); + el.id = "combat-banner"; el.style.cssText = ` position:fixed;top:18%;left:50%;transform:translateX(-50%); background:rgba(10,8,5,0.92);border:1px solid ${color}; @@ -699,8 +803,10 @@ function flashSlot(slotId, color, durationMs = 300) { const el = document.getElementById(slotId); if (!el) return; el.style.transition = `box-shadow ${durationMs / 2}ms ease`; - el.style.boxShadow = `0 0 18px 6px ${color}`; - setTimeout(() => { el.style.boxShadow = ''; }, durationMs); + el.style.boxShadow = `0 0 18px 6px ${color}`; + setTimeout(() => { + el.style.boxShadow = ""; + }, durationMs); } /* Karte bewegen: Slot A → Slot B */ @@ -709,7 +815,7 @@ function applyMoveEvent(ev) { if (!entry) return; const fromEl = document.getElementById(ev.from); - const toEl = document.getElementById(ev.to); + const toEl = document.getElementById(ev.to); if (!fromEl || !toEl) return; /* boardState aktualisieren */ @@ -717,8 +823,11 @@ function applyMoveEvent(ev) { boardState[ev.to] = entry; /* DOM aktualisieren */ - fromEl.classList.remove('slot-occupied'); - fromEl.innerHTML = '\u2736' + ev.from.split('-slot-')[1] + ''; + fromEl.classList.remove("slot-occupied"); + fromEl.innerHTML = + '\u2736' + + ev.from.split("-slot-")[1] + + ""; renderCardOnBoard(toEl, entry.card, entry.owner); @@ -728,35 +837,37 @@ function applyMoveEvent(ev) { /* Angriff: Verteidigung des Ziels aktualisieren */ function applyAttackEvent(ev) { const attackerEntry = boardState[ev.from]; - const targetEntry = boardState[ev.to]; + const targetEntry = boardState[ev.to]; if (!attackerEntry || !targetEntry) return; /* Verteidigung im boardState aktualisieren */ targetEntry.card = { ...targetEntry.card, defends: ev.remainingDef }; /* Visuelles Feedback */ - flashSlot(ev.from, 'rgba(255,200,50,0.7)', 250); // Angreifer leuchtet gold - flashSlot(ev.to, 'rgba(220,50,50,0.85)', 350); // Ziel leuchtet rot + flashSlot(ev.from, "rgba(255,200,50,0.7)", 250); // Angreifer leuchtet gold + flashSlot(ev.to, "rgba(220,50,50,0.85)", 350); // Ziel leuchtet rot /* Verteidigungswert im DOM sofort aktualisieren */ const targetEl = document.getElementById(ev.to); if (targetEl) { - const defEl = targetEl.querySelector('.cs-def'); + const defEl = targetEl.querySelector(".cs-def"); if (defEl) { defEl.textContent = ev.remainingDef; - defEl.style.color = ev.remainingDef <= 2 ? '#e74c3c' : ''; + defEl.style.color = ev.remainingDef <= 2 ? "#e74c3c" : ""; } } - console.log(`[Combat] Angriff: ${attackerEntry.card.name} → ${targetEntry.card.name} (${ev.damage} Schaden, verbleibend: ${ev.remainingDef})`); + console.log( + `[Combat] Angriff: ${attackerEntry.card.name} → ${targetEntry.card.name} (${ev.damage} Schaden, verbleibend: ${ev.remainingDef})`, + ); } /* Karte stirbt */ function applyDieEvent(ev) { const entry = boardState[ev.slotId]; - const name = entry?.card?.name ?? '???'; + const name = entry?.card?.name ?? "???"; - flashSlot(ev.slotId, 'rgba(200,50,50,0.9)', 500); + flashSlot(ev.slotId, "rgba(200,50,50,0.9)", 500); setTimeout(() => { clearBoardSlot(ev.slotId); @@ -767,11 +878,11 @@ function applyDieEvent(ev) { /* Finales Board nach Kampfphase vollständig anwenden (Sicherheits-Sync) */ function applyFinalBoard(finalBoard) { /* Erst alles leeren */ - Object.keys(boardState).forEach(sid => clearBoardSlot(sid)); + Object.keys(boardState).forEach((sid) => clearBoardSlot(sid)); /* Dann neuen Zustand setzen */ if (!finalBoard || !finalBoard.length) return; - finalBoard.forEach(entry => { + finalBoard.forEach((entry) => { const slotEl = document.getElementById(entry.boardSlot); if (!slotEl) return; boardState[entry.boardSlot] = { card: entry.card, owner: entry.owner }; @@ -780,12 +891,12 @@ function applyFinalBoard(finalBoard) { } /* Haupt-Handler für combat_phase */ -socket.on('combat_phase', data => { - const events = data.events ?? []; +socket.on("combat_phase", (data) => { + const events = data.events ?? []; const finalBoard = data.finalBoard ?? []; console.log(`[Combat] Kampfphase startet: ${events.length} Events`); - showCombatBanner('\u2694\uFE0F KAMPFPHASE'); + showCombatBanner("\u2694\uFE0F KAMPFPHASE"); if (events.length === 0) { /* Keine Karten im Spiel → direkt final sync */ @@ -796,18 +907,16 @@ socket.on('combat_phase', data => { /* Events sequenziell abarbeiten */ let delay = 600; // kurze Pause nach Banner - events.forEach(ev => { + events.forEach((ev) => { const thisDelay = delay; - if (ev.type === 'move') { + if (ev.type === "move") { delay += COMBAT_DELAY_MOVE; setTimeout(() => applyMoveEvent(ev), thisDelay); - - } else if (ev.type === 'attack') { + } else if (ev.type === "attack") { delay += COMBAT_DELAY_ATTACK; setTimeout(() => applyAttackEvent(ev), thisDelay); - - } else if (ev.type === 'die') { + } else if (ev.type === "die") { delay += COMBAT_DELAY_DIE; setTimeout(() => applyDieEvent(ev), thisDelay); } @@ -817,41 +926,45 @@ socket.on('combat_phase', data => { delay += 500; setTimeout(() => { applyFinalBoard(finalBoard); - console.log('[Combat] Kampfphase abgeschlossen, Board synchronisiert.'); + console.log("[Combat] Kampfphase abgeschlossen, Board synchronisiert."); }, delay); }); - /* ═══════════════════════════════════════════════════════════ AVATAR HP – Anzeige & Animationen ═══════════════════════════════════════════════════════════ */ function updateHpDisplay(slot, currentHp, maxHp) { - const isLeft = slot === (window._leftSlot || 'player1'); - const orbEl = document.getElementById(isLeft ? 'orbLeft' : 'orbRight'); - const hpEl = document.getElementById(isLeft ? 'hpLeft' : 'hpRight'); - const avEl = document.getElementById(isLeft ? 'avLeft' : 'avRight'); + const isLeft = slot === (window._leftSlot || "player1"); + const orbEl = document.getElementById(isLeft ? "orbLeft" : "orbRight"); + const hpEl = document.getElementById(isLeft ? "hpLeft" : "hpRight"); + const avEl = document.getElementById(isLeft ? "avLeft" : "avRight"); if (orbEl) orbEl.textContent = currentHp; - if (hpEl) hpEl.textContent = currentHp; + if (hpEl) hpEl.textContent = currentHp; /* Farbe je nach HP-Prozent */ - const pct = maxHp > 0 ? currentHp / maxHp : 0; - const color = pct > 0.5 ? '#e74c3c' : pct > 0.25 ? '#e67e22' : '#8b0000'; + const pct = maxHp > 0 ? currentHp / maxHp : 0; + const color = pct > 0.5 ? "#e74c3c" : pct > 0.25 ? "#e67e22" : "#8b0000"; if (orbEl) { orbEl.style.background = `radial-gradient(circle at 40% 35%, ${color}, #3a0000)`; - orbEl.style.boxShadow = `0 0 12px ${color}cc`; + orbEl.style.boxShadow = `0 0 12px ${color}cc`; } /* Avatar-Schüttelanimation + roter Flash */ if (avEl) { - avEl.style.transition = 'transform 0.07s ease'; - avEl.style.transform = 'scale(1.07)'; - setTimeout(() => { avEl.style.transform = 'scale(0.96)'; }, 70); - setTimeout(() => { avEl.style.transform = ''; }, 150); + avEl.style.transition = "transform 0.07s ease"; + avEl.style.transform = "scale(1.07)"; + setTimeout(() => { + avEl.style.transform = "scale(0.96)"; + }, 70); + setTimeout(() => { + avEl.style.transform = ""; + }, 150); - const flash = document.createElement('div'); - flash.style.cssText = 'position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;'; + const flash = document.createElement("div"); + flash.style.cssText = + "position:absolute;inset:0;border-radius:inherit;background:rgba(220,50,50,0.4);z-index:20;pointer-events:none;"; avEl.appendChild(flash); setTimeout(() => flash.remove(), 320); } @@ -859,28 +972,29 @@ function updateHpDisplay(slot, currentHp, maxHp) { function applyHpFromEvent(data) { if (!data.hp || !data.maxHp) return; - ['player1','player2'].forEach(slot => { - if (data.hp[slot] != null) updateHpDisplay(slot, data.hp[slot], data.maxHp[slot] ?? data.hp[slot]); + ["player1", "player2"].forEach((slot) => { + if (data.hp[slot] != null) + updateHpDisplay(slot, data.hp[slot], data.maxHp[slot] ?? data.hp[slot]); }); } /* Initiale HP beim Spielstart */ -socket.on('hp_init', data => { +socket.on("hp_init", (data) => { applyHpFromEvent(data); - console.log('[HP] Init:', data.hp); + console.log("[HP] Init:", data.hp); }); /* Avatar getroffen */ -socket.on('avatar_damaged', data => { +socket.on("avatar_damaged", (data) => { const { slot, damage, remainingHp, maxHp } = data; updateHpDisplay(slot, remainingHp, maxHp); console.log(`[HP] ${slot} -${damage} → ${remainingHp}/${maxHp}`); /* Schadens-Zahl einblenden */ - const isLeft = slot === (window._leftSlot || 'player1'); - const avEl = document.getElementById(isLeft ? 'avLeft' : 'avRight'); + const isLeft = slot === (window._leftSlot || "player1"); + const avEl = document.getElementById(isLeft ? "avLeft" : "avRight"); if (avEl) { - const dmg = document.createElement('div'); + const dmg = document.createElement("div"); dmg.textContent = `-${damage}`; dmg.style.cssText = ` position:absolute;top:15%;left:50%;transform:translateX(-50%); @@ -898,17 +1012,18 @@ socket.on('avatar_damaged', data => { ═══════════════════════════════════════════════════════════ */ function closeToArena() { if (window.parent && window.parent !== window) { - window.parent.document.getElementById('arena-backdrop')?.remove(); - window.parent.document.getElementById('arena-popup')?.remove(); + window.parent.document.getElementById("arena-backdrop")?.remove(); + window.parent.document.getElementById("arena-popup")?.remove(); } else { - window.location.href = '/launcher'; + window.location.href = "/launcher"; } } function handleAufgeben() { - const modal = document.createElement('div'); - modal.id = 'surrender-modal'; - modal.style.cssText = 'position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;'; + const modal = document.createElement("div"); + modal.id = "surrender-modal"; + modal.style.cssText = + "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.8);display:flex;align-items:center;justify-content:center;"; modal.innerHTML = `
\uD83C\uDFF3\uFE0F
@@ -920,18 +1035,21 @@ function handleAufgeben() {
`; document.body.appendChild(modal); - document.getElementById('surrender-no').addEventListener('click', () => modal.remove()); - document.getElementById('surrender-yes').addEventListener('click', () => { + document + .getElementById("surrender-no") + .addEventListener("click", () => modal.remove()); + document.getElementById("surrender-yes").addEventListener("click", () => { modal.remove(); - socket.emit('player_surrender', { matchId, slot: mySlot }); + socket.emit("player_surrender", { matchId, slot: mySlot }); showSurrenderMessage(false); }); } function showSurrenderMessage(iWon) { - const overlay = document.createElement('div'); - overlay.id = 'surrender-result-overlay'; - overlay.style.cssText = "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:'Cinzel',serif;animation:fadeIn 0.4s ease;"; + const overlay = document.createElement("div"); + overlay.id = "surrender-result-overlay"; + overlay.style.cssText = + "position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,0.92);display:flex;flex-direction:column;align-items:center;justify-content:center;font-family:'Cinzel',serif;animation:fadeIn 0.4s ease;"; overlay.innerHTML = iWon ? `
\uD83C\uDFC6
SIEG!
@@ -944,11 +1062,13 @@ function showSurrenderMessage(iWon) {
Keine Punkte f\u00FCr dieses Match.
`; document.body.appendChild(overlay); - document.getElementById('surrender-close-btn').addEventListener('click', closeToArena); + document + .getElementById("surrender-close-btn") + .addEventListener("click", closeToArena); setTimeout(closeToArena, 8000); } -socket.on('player_surrendered', data => { +socket.on("player_surrendered", (data) => { if (data.slot !== mySlot) showSurrenderMessage(true); }); @@ -956,27 +1076,27 @@ socket.on('player_surrendered', data => { MATCH-ERGEBNIS OVERLAY ═══════════════════════════════════════════════════════════ */ function showResultOverlay(won, data) { - const overlay = document.getElementById('match-result-overlay'); - const titleEl = document.getElementById('result-title'); - const pointsEl = document.getElementById('result-points'); - titleEl.textContent = won ? '\u2694\uFE0F SIEG!' : '\uD83D\uDC80 NIEDERLAGE'; - titleEl.className = 'result-title ' + (won ? 'win' : 'lose'); - pointsEl.textContent = 'Punkte werden berechnet\u2026'; - overlay.classList.add('show'); + const overlay = document.getElementById("match-result-overlay"); + const titleEl = document.getElementById("result-title"); + const pointsEl = document.getElementById("result-points"); + titleEl.textContent = won ? "\u2694\uFE0F SIEG!" : "\uD83D\uDC80 NIEDERLAGE"; + titleEl.className = "result-title " + (won ? "win" : "lose"); + pointsEl.textContent = "Punkte werden berechnet\u2026"; + overlay.classList.add("show"); if (data) updateResultWithPoints(data); } function updateResultWithPoints(data) { /* Verhindert doppeltes Aufrufen */ - if (document.getElementById('match-end-overlay')) return; + if (document.getElementById("match-end-overlay")) return; - const img = data.won ? '/images/victory.jpeg' : '/images/defeat.jpeg'; + const img = data.won ? "/images/victory.jpeg" : "/images/defeat.jpg"; const awarded = data.awarded ?? 0; /* Fade-in Keyframe einmalig anlegen */ - if (!document.getElementById('_matchEndStyle')) { - const st = document.createElement('style'); - st.id = '_matchEndStyle'; + if (!document.getElementById("_matchEndStyle")) { + const st = document.createElement("style"); + st.id = "_matchEndStyle"; st.textContent = ` @keyframes matchEndFadeIn { from{opacity:0} to{opacity:1} } @keyframes matchPtsSlideUp { from{opacity:0;transform:translateY(20px)} to{opacity:1;transform:translateY(0)} }`; @@ -984,16 +1104,14 @@ function updateResultWithPoints(data) { } /* Punkte-Text aufbauen */ - const ptsLine = awarded > 0 - ? '+' + awarded + ' Arena-Punkte' - : 'Keine Punkte'; - const lvlLine = data.level_up - ? '⬆ LEVEL UP! → Level ' + data.new_level - : ''; + const ptsLine = + awarded > 0 ? "+" + awarded + " Arena-Punkte" : "Keine Punkte"; + const lvlLine = data.level_up ? "⬆ LEVEL UP! → Level " + data.new_level : ""; - const overlay = document.createElement('div'); - overlay.id = 'match-end-overlay'; - overlay.style.cssText = 'position:fixed;inset:0;z-index:9999;background:#000;animation:matchEndFadeIn 0.5s ease forwards;'; + const overlay = document.createElement("div"); + overlay.id = "match-end-overlay"; + overlay.style.cssText = + "position:fixed;inset:0;z-index:9999;background:#000;animation:matchEndFadeIn 0.5s ease forwards;"; overlay.innerHTML = `
${ptsLine}
- ${lvlLine ? `
${lvlLine}
` : ''} + ${lvlLine ? `
${lvlLine}
` : ""}
`; document.body.appendChild(overlay); /* Aktuellen Punktestand nachladen und anzeigen */ - fetch('/api/points/me').then(r => r.json()).then(me => { - const info = overlay.querySelector('div > div:first-child'); - if (info && awarded > 0) { - info.insertAdjacentHTML('afterend', - `
Gesamt: ${me.arena_points} Pts • Level ${me.level}
` - ); - } - }).catch(() => {}); + fetch("/api/points/me") + .then((r) => r.json()) + .then((me) => { + const info = overlay.querySelector("div > div:first-child"); + if (info && awarded > 0) { + info.insertAdjacentHTML( + "afterend", + `
Gesamt: ${me.arena_points} Pts • Level ${me.level}
`, + ); + } + }) + .catch(() => {}); /* Nach 3 Sekunden zur Arena weiterleiten */ setTimeout(() => closeToArena(), 3000); } -function closePopup() { closeToArena(); } +function closePopup() { + closeToArena(); +} -socket.on('match_result', data => updateResultWithPoints(data)); -socket.on('match_cancelled', () => closePopup()); +socket.on("match_result", (data) => updateResultWithPoints(data)); +socket.on("match_cancelled", () => closePopup()); -document.getElementById('result-close-btn').addEventListener('click', closePopup); +document + .getElementById("result-close-btn") + .addEventListener("click", closePopup); /* ═══════════════════════════════════════════════════════════ AVATAR @@ -1036,11 +1162,14 @@ function loadAvatar(input, imgId, parentId) { const file = input.files[0]; if (!file) return; const r = new FileReader(); - r.onload = e => { + r.onload = (e) => { const img = document.getElementById(imgId); img.src = e.target.result; - img.style.display = 'block'; - document.getElementById(parentId)?.querySelector('.av-placeholder')?.style.setProperty('display', 'none'); + img.style.display = "block"; + document + .getElementById(parentId) + ?.querySelector(".av-placeholder") + ?.style.setProperty("display", "none"); }; r.readAsDataURL(file); } @@ -1048,7 +1177,17 @@ function loadAvatar(input, imgId, parentId) { /* ═══════════════════════════════════════════════════════════ EVENT-LISTENER ═══════════════════════════════════════════════════════════ */ -document.getElementById('bereit-btn')?.addEventListener('click', handleBereit); -document.getElementById('aufgeben-btn')?.addEventListener('click', handleAufgeben); -document.getElementById('fileInputLeft')?.addEventListener('change', function () { loadAvatar(this, 'avImgL', 'avLeft'); }); -document.getElementById('fileInputRight')?.addEventListener('change', function () { loadAvatar(this, 'avImgR', 'avRight'); }); +document.getElementById("bereit-btn")?.addEventListener("click", handleBereit); +document + .getElementById("aufgeben-btn") + ?.addEventListener("click", handleAufgeben); +document + .getElementById("fileInputLeft") + ?.addEventListener("change", function () { + loadAvatar(this, "avImgL", "avLeft"); + }); +document + .getElementById("fileInputRight") + ?.addEventListener("change", function () { + loadAvatar(this, "avImgR", "avRight"); + });