ergte
This commit is contained in:
parent
e54d9c095f
commit
3f5269862b
@ -417,11 +417,13 @@ body {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease, transform 0.2s ease;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 calc(var(--s) * 4) calc(var(--s) * 14) rgba(0,0,0,.35);
|
||||
box-shadow: 0 calc(var(--s) * 4) calc(var(--s) * 14) rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
.hand-slot:hover {
|
||||
border-color: rgba(255, 215, 80, 0.9);
|
||||
@ -432,7 +434,6 @@ body {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
|
||||
/* Right side: action icons */
|
||||
.action-hud {
|
||||
display: flex;
|
||||
@ -954,7 +955,202 @@ body {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
|
||||
.hand-slot img.hand-card-img {
|
||||
width:100%;height:100%;object-fit:cover;display:block;border-radius:calc(var(--s) * 7);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
border-radius: calc(var(--s) * 7);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════
|
||||
MATCH-RESULT OVERLAY
|
||||
══════════════════════════════════════════════════════ */
|
||||
#match-result-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
z-index: 9999;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: "Cinzel", serif;
|
||||
animation: fadeIn 0.4s ease;
|
||||
}
|
||||
#match-result-overlay.show {
|
||||
display: flex;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.result-title {
|
||||
font-size: 48px;
|
||||
letter-spacing: 6px;
|
||||
margin-bottom: 12px;
|
||||
text-shadow: 0 0 30px currentColor;
|
||||
}
|
||||
.result-title.win {
|
||||
color: #f0d060;
|
||||
}
|
||||
.result-title.lose {
|
||||
color: #c84040;
|
||||
}
|
||||
.result-points {
|
||||
font-size: 22px;
|
||||
color: #c8a860;
|
||||
margin-bottom: 6px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.result-levelup {
|
||||
font-size: 18px;
|
||||
color: #60e060;
|
||||
margin-bottom: 20px;
|
||||
letter-spacing: 2px;
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
.result-progress-wrap {
|
||||
width: 320px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.result-progress-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #a08060;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.result-progress-track {
|
||||
height: 10px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.result-progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #c8960c, #f0d060);
|
||||
border-radius: 5px;
|
||||
transition: width 1s ease;
|
||||
}
|
||||
.result-close-btn {
|
||||
background: linear-gradient(#4a3010, #2a1a08);
|
||||
border: 2px solid #c8960c;
|
||||
color: #f0d9a6;
|
||||
font-family: "Cinzel", serif;
|
||||
font-size: 14px;
|
||||
padding: 10px 30px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 2px;
|
||||
margin-top: 10px;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.result-close-btn:hover {
|
||||
border-color: #f0d060;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════
|
||||
DECK-STAPEL
|
||||
══════════════════════════════════════════════════════ */
|
||||
.hand-slot-deck {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
.deck-stack-wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100% - 18px);
|
||||
}
|
||||
.deck-card-back,
|
||||
.deck-card-top {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.deck-shadow-3 {
|
||||
transform: translate(-4px, -4px);
|
||||
}
|
||||
.deck-shadow-2 {
|
||||
transform: translate(-2px, -2px);
|
||||
}
|
||||
.deck-shadow-1 {
|
||||
transform: translate(-1px, -1px);
|
||||
}
|
||||
.deck-card-top {
|
||||
transform: translate(0, 0);
|
||||
border: 1px solid rgba(200, 150, 42, 0.5);
|
||||
}
|
||||
.deck-count {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
font-family: "Cinzel", serif;
|
||||
font-size: 10px;
|
||||
color: #f0d060;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.hand-slot-card {
|
||||
border: 1px solid rgba(200, 150, 42, 0.4) !important;
|
||||
}
|
||||
.hand-slot-card > img {
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hand-slot-card:hover {
|
||||
border-color: rgba(200, 150, 42, 0.9) !important;
|
||||
transform: translateY(-4px);
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════
|
||||
REICHWEITE & LAUFEN BADGES (Hand + Board)
|
||||
══════════════════════════════════════════════════════ */
|
||||
.cs-range,
|
||||
.cs-race {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 20px;
|
||||
font-family: "Cinzel", serif;
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
z-index: 6;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cs-range {
|
||||
bottom: 22px;
|
||||
left: 3px;
|
||||
background: rgba(30, 20, 0, 0.85);
|
||||
border: 1px solid #e8b84b;
|
||||
color: #e8b84b;
|
||||
}
|
||||
.cs-race {
|
||||
bottom: 6px;
|
||||
left: 3px;
|
||||
background: rgba(0, 25, 0, 0.85);
|
||||
border: 1px solid #7de87d;
|
||||
color: #7de87d;
|
||||
}
|
||||
|
||||
724
public/js/buildings/1v1.js
Normal file
724
public/js/buildings/1v1.js
Normal file
@ -0,0 +1,724 @@
|
||||
/* ═══════════════════════════════════════════════════════════════
|
||||
public/js/buildings/1v1.js
|
||||
Vollstaendige Spielfeld-Logik fuer das 1v1-Battlefield.
|
||||
EJS-Variablen kommen aus window.GAME_CONFIG (in der EJS gesetzt).
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── 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';
|
||||
|
||||
/* ── Gegner-Name aus URL setzen ──────────────────────────── */
|
||||
const _urlParams = new URLSearchParams(window.location.search);
|
||||
const opponentNameFromUrl = _urlParams.get('opponent');
|
||||
if (opponentNameFromUrl) {
|
||||
const oppEl = document.getElementById(amIPlayer1 ? 'nameRight' : 'nameLeft');
|
||||
if (oppEl) oppEl.textContent = decodeURIComponent(opponentNameFromUrl);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
SPIELFELD AUFBAUEN
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
['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;
|
||||
s.dataset.slotIndex = i;
|
||||
s.id = `${rowId}-slot-${i}`;
|
||||
s.innerHTML = '<span class="slot-icon">\u2736</span><span class="slot-num">' + i + '</span>';
|
||||
row.appendChild(s);
|
||||
}
|
||||
});
|
||||
|
||||
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';
|
||||
deckSlot.innerHTML = `
|
||||
<div class="deck-stack-wrap">
|
||||
<img class="deck-card-back deck-shadow-3" src="/images/items/rueckseite.png">
|
||||
<img class="deck-card-back deck-shadow-2" src="/images/items/rueckseite.png">
|
||||
<img class="deck-card-back deck-shadow-1" src="/images/items/rueckseite.png">
|
||||
<img class="deck-card-top" src="/images/items/rueckseite.png">
|
||||
</div>
|
||||
<span class="deck-count" id="deck-count">\u2014</span>`;
|
||||
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;
|
||||
s.innerHTML = '<span class="hs-icon">\uD83C\uDCCF</span>';
|
||||
hand.appendChild(s);
|
||||
});
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
HAND & DECK SYSTEM
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
let deckQueue = [];
|
||||
const handSlotState = {};
|
||||
handCardIds.forEach(id => { handSlotState[id] = null; });
|
||||
|
||||
/* ── SVG-Icons ───────────────────────────────────────────── */
|
||||
const SVG_RANGE = `<svg viewBox="0 0 16 16" width="10" height="10" style="display:inline;vertical-align:middle;flex-shrink:0" fill="none" stroke="#e8b84b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2 Q1 8 4 14"/><line x1="4" y1="2" x2="4" y2="14" stroke-width="0.7" stroke-dasharray="2,1.5"/><line x1="4" y1="8" x2="13" y2="8"/><polyline points="11,6 13,8 11,10"/><line x1="5" y1="7" x2="4" y2="8"/><line x1="5" y1="9" x2="4" y2="8"/></svg>`;
|
||||
const SVG_RACE = `<svg viewBox="0 0 16 16" width="10" height="10" style="display:inline;vertical-align:middle;flex-shrink:0" fill="none" stroke="#7de87d" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="2.5" r="1.4" fill="#7de87d" stroke="none"/><line x1="9" y1="4" x2="8" y2="9"/><line x1="8" y1="9" x2="10" y2="14"/><line x1="8" y1="9" x2="6" y2="13"/><line x1="8.5" y1="5.5" x2="11" y2="8"/><line x1="8.5" y1="5.5" x2="6" y2="7"/></svg>`;
|
||||
|
||||
/* ── Hand-Slot rendern ───────────────────────────────────── */
|
||||
function renderHandSlot(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');
|
||||
|
||||
if (!state) {
|
||||
slot.innerHTML = '<span class="hs-icon">\uD83C\uDCCF</span>';
|
||||
slot.draggable = false;
|
||||
delete slot.dataset.cardSlotId;
|
||||
return;
|
||||
}
|
||||
|
||||
const { card, currentCd } = state;
|
||||
const isReady = currentCd <= 0;
|
||||
|
||||
const atkVal = card.attack ?? null;
|
||||
const defVal = card.defends ?? null;
|
||||
const rngVal = card.range ?? null;
|
||||
const rceVal = card.race ?? null;
|
||||
|
||||
const statsHtml = `<div class="card-stat-overlay">
|
||||
${atkVal != null ? `<span class="cs-atk">${atkVal}</span>` : ''}
|
||||
${defVal != null ? `<span class="cs-def">${defVal}</span>` : ''}
|
||||
${card.cooldown != null ? `<span class="cs-cd ${isReady ? 'cs-cd-ready' : ''}">${isReady ? '\u2713' : currentCd}</span>` : ''}
|
||||
${rngVal != null ? `<span class="cs-range">${SVG_RANGE} ${rngVal}</span>` : ''}
|
||||
${rceVal != null ? `<span class="cs-race">${SVG_RACE} ${rceVal}</span>` : ''}
|
||||
</div>`;
|
||||
|
||||
const readyBadge = (isReady && isMyTurn) ? '<div class="hand-slot-ready-badge">SPIELEN</div>' : '';
|
||||
|
||||
slot.innerHTML = card.image
|
||||
? `<img class="hand-card-img" src="/images/cards/${card.image}" onerror="this.src='/images/items/rueckseite.png'" title="${card.name}">${statsHtml}${readyBadge}`
|
||||
: `<div style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;font-family:Cinzel,serif;">
|
||||
<span style="font-size:18px;">\u2694\uFE0F</span>
|
||||
<span style="font-size:9px;color:#f0d9a6;text-align:center;">${card.name}</span>
|
||||
</div>${statsHtml}${readyBadge}`;
|
||||
|
||||
slot.classList.toggle('hand-slot-ready', isReady);
|
||||
slot.classList.add('hand-slot--filled');
|
||||
|
||||
if (isReady && isMyTurn) {
|
||||
slot.draggable = true;
|
||||
slot.dataset.cardSlotId = id;
|
||||
} else {
|
||||
slot.draggable = false;
|
||||
delete slot.dataset.cardSlotId;
|
||||
}
|
||||
}
|
||||
|
||||
function setHandSlot(id, card) {
|
||||
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);
|
||||
if (!freeSlot) return;
|
||||
const card = deckQueue.shift();
|
||||
setHandSlot(freeSlot, card);
|
||||
const countEl = document.getElementById('deck-count');
|
||||
if (countEl) countEl.textContent = deckQueue.length;
|
||||
}
|
||||
|
||||
function tickHandCooldowns() {
|
||||
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.wasReduced = true;
|
||||
} else if (state.currentCd > 0) {
|
||||
state.currentCd--;
|
||||
}
|
||||
renderHandSlot(id);
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Deck laden ──────────────────────────────────────────── */
|
||||
(async () => {
|
||||
try {
|
||||
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');
|
||||
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); });
|
||||
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]); }
|
||||
deckQueue = expanded.slice(3);
|
||||
const countEl = document.getElementById('deck-count');
|
||||
if (countEl) countEl.textContent = deckQueue.length;
|
||||
} 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;
|
||||
|
||||
function startTurnTimer(activeName) {
|
||||
clearInterval(turnTimerInt);
|
||||
turnSecsLeft = TURN_SECONDS;
|
||||
updateTimerUI(turnSecsLeft, activeName);
|
||||
const wrap = document.getElementById('turn-timer-wrap');
|
||||
if (wrap) wrap.style.display = 'flex';
|
||||
turnTimerInt = setInterval(() => {
|
||||
turnSecsLeft--;
|
||||
updateTimerUI(turnSecsLeft, activeName);
|
||||
if (turnSecsLeft <= 0) {
|
||||
clearInterval(turnTimerInt);
|
||||
if (isMyTurn) endMyTurn();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function stopTurnTimer() {
|
||||
clearInterval(turnTimerInt);
|
||||
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';
|
||||
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'; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
ZUG-SYSTEM
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
function setTurnState(myTurn, activeName) {
|
||||
isMyTurn = myTurn;
|
||||
const btn = document.getElementById('end-turn-btn');
|
||||
if (!btn) return;
|
||||
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');
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
btn.style.opacity = '0.4';
|
||||
showTurnIndicator(activeName);
|
||||
startTurnTimer(activeName || 'Gegner');
|
||||
}
|
||||
}
|
||||
|
||||
function showTurnIndicator(activeName) {
|
||||
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));
|
||||
border:1px solid rgba(200,160,60,0.5);border-radius:calc(var(--s)*8);
|
||||
color:rgba(255,215,80,0.75);font-family:'Cinzel',serif;
|
||||
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';
|
||||
document.body.appendChild(ind);
|
||||
}
|
||||
|
||||
function endMyTurn() {
|
||||
if (!isMyTurn) return;
|
||||
clearInterval(turnTimerInt);
|
||||
stopTurnTimer();
|
||||
setTurnState(false);
|
||||
tickHandCooldowns();
|
||||
drawNextCard();
|
||||
socket.emit('end_turn', { matchId, slot: mySlot });
|
||||
}
|
||||
|
||||
document.getElementById('end-turn-btn')?.addEventListener('click', endMyTurn);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
KARTE AUF BOARD RENDERN
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
const boardState = {};
|
||||
|
||||
function buildStatsHtml(card) {
|
||||
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;
|
||||
return `<div class="card-stat-overlay">
|
||||
${atk != null ? `<span class="cs-atk">${atk}</span>` : ''}
|
||||
${def != null ? `<span class="cs-def">${def}</span>` : ''}
|
||||
${cd != null ? `<span class="cs-cd">${cd}</span>` : ''}
|
||||
${rng != null ? `<span class="cs-range">${SVG_RANGE} ${rng}</span>` : ''}
|
||||
${rce != null ? `<span class="cs-race">${SVG_RACE} ${rce}</span>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderCardOnBoard(slotEl, card) {
|
||||
const statsHtml = buildStatsHtml(card);
|
||||
slotEl.classList.add('slot-occupied');
|
||||
slotEl.innerHTML = card.image
|
||||
? `<img src="/images/cards/${card.image}" onerror="this.src='/images/items/rueckseite.png'" title="${card.name}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;display:block;">${statsHtml}`
|
||||
: `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:4px;font-family:Cinzel,serif;padding:4px;"><span style="font-size:18px;">\u2694\uFE0F</span><span style="font-size:9px;color:#f0d9a6;text-align:center;">${card.name}</span></div>${statsHtml}`;
|
||||
}
|
||||
|
||||
function renderCardInSlot(slot, card) {
|
||||
if (!slot || !card) return;
|
||||
slot.classList.add('slot-occupied');
|
||||
slot.innerHTML = card.image
|
||||
? `<img src="/images/cards/${card.image}" onerror="this.src='/images/items/rueckseite.png'" title="${card.name}" style="width:100%;height:100%;object-fit:cover;border-radius:7px;display:block;">${buildStatsHtml(card)}`
|
||||
: `<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:4px;font-family:Cinzel,serif;padding:4px;"><span style="font-size:18px;">\u2694\uFE0F</span><span style="font-size:9px;color:#f0d9a6;text-align:center;">${card.name}</span></div>${buildStatsHtml(card)}`;
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
SOCKET & ARENA-JOIN
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
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');
|
||||
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 });
|
||||
}
|
||||
|
||||
function fetchAndJoin() {
|
||||
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 });
|
||||
} else {
|
||||
fetchAndJoin();
|
||||
}
|
||||
});
|
||||
|
||||
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';
|
||||
}, 10000);
|
||||
|
||||
/* ── Board-Sync ──────────────────────────────────────────── */
|
||||
function applyBoardSync(cards) {
|
||||
if (!cards || !cards.length) return;
|
||||
cards.forEach(cd => {
|
||||
const slotEl = document.getElementById(cd.boardSlot);
|
||||
if (!slotEl || boardState[cd.boardSlot]) return;
|
||||
boardState[cd.boardSlot] = cd.card;
|
||||
renderCardOnBoard(slotEl, cd.card);
|
||||
});
|
||||
console.log('[1v1] Board sync:', cards.length, 'Karten');
|
||||
}
|
||||
|
||||
/* ── Socket Events ───────────────────────────────────────── */
|
||||
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';
|
||||
});
|
||||
|
||||
socket.on('arena_ready', data => {
|
||||
console.log('[Arena] arena_ready:', data);
|
||||
clearTimeout(readyFallbackTimer);
|
||||
document.getElementById('connecting-overlay')?.remove();
|
||||
if (data.boardSync) applyBoardSync(data.boardSync);
|
||||
|
||||
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)) {
|
||||
oppEl.textContent = oppName;
|
||||
} else if (oppEl && urlOppName && (!oppEl.textContent || oppEl.textContent === 'Gegner')) {
|
||||
oppEl.textContent = decodeURIComponent(urlOppName);
|
||||
}
|
||||
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';
|
||||
});
|
||||
|
||||
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();
|
||||
if (data.boardSync) applyBoardSync(data.boardSync);
|
||||
const activeSlot = data.activeSlot;
|
||||
const nowMyTurn = activeSlot === mySlot;
|
||||
const activeNameEl = document.getElementById(activeSlot === 'player1' ? 'nameLeft' : 'nameRight');
|
||||
const activeName = activeNameEl?.textContent || (nowMyTurn ? 'Du' : 'Gegner');
|
||||
console.log(`[1v1] turn_change: ${activeSlot} | meinZug: ${nowMyTurn}`);
|
||||
setTurnState(nowMyTurn, activeName);
|
||||
});
|
||||
|
||||
socket.on('turn_started', data => {
|
||||
const myT = data.slot === mySlot;
|
||||
const nameEl = document.getElementById(data.slot === 'player1' ? 'nameLeft' : 'nameRight');
|
||||
setTurnState(myT, nameEl?.textContent || (myT ? 'Du' : 'Gegner'));
|
||||
});
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
BEREIT-SYSTEM
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
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');
|
||||
btn.disabled = true;
|
||||
socket.emit('player_ready', { matchId, slot: mySlot });
|
||||
}
|
||||
|
||||
const CIRCUMFERENCE = 2 * Math.PI * 34;
|
||||
const timerCircle = document.getElementById('timer-circle');
|
||||
if (timerCircle) timerCircle.style.strokeDasharray = CIRCUMFERENCE;
|
||||
|
||||
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';
|
||||
}
|
||||
});
|
||||
|
||||
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.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 rightName = flip ? p1Name : p2Name;
|
||||
|
||||
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 = `<div style="font-family:'Cinzel',serif;font-size:calc(var(--s)*13);font-weight:700;color:#ffd750;text-align:center;padding:0 8px;word-break:break-word;line-height:1.4;text-shadow:0 2px 8px rgba(0,0,0,0.9)">${name}</div>`;
|
||||
});
|
||||
|
||||
document.getElementById('board-lock-overlay')?.remove();
|
||||
amILeftPlayer = flip ? mySlot === 'player2' : mySlot === 'player1';
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
const leftSlot = flip ? 'player2' : 'player1';
|
||||
window._leftSlot = leftSlot;
|
||||
console.log(`[1v1] linker Slot: ${leftSlot} | ich: ${mySlot}`);
|
||||
if (mySlot === 'player1') {
|
||||
socket.emit('start_turn_request', { matchId, starterSlot: leftSlot });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
DRAG & DROP
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
function isMyZone(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 => {
|
||||
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');
|
||||
});
|
||||
}
|
||||
|
||||
let draggedCardSlotId = null;
|
||||
|
||||
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);
|
||||
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');
|
||||
setDropZones(false);
|
||||
draggedCardSlotId = null;
|
||||
});
|
||||
|
||||
['row1', 'row2'].forEach(rowId => {
|
||||
const row = document.getElementById(rowId);
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
row.addEventListener('dragleave', e => {
|
||||
const slot = e.target.closest('.card-slot');
|
||||
if (slot) slot.classList.remove('drop-zone-hover');
|
||||
});
|
||||
|
||||
row.addEventListener('drop', e => {
|
||||
e.preventDefault();
|
||||
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');
|
||||
if (!sourceId) return;
|
||||
const cardState = handSlotState[sourceId];
|
||||
if (!cardState || cardState.currentCd > 0) return;
|
||||
|
||||
handSlotState[sourceId] = null;
|
||||
renderHandSlot(sourceId);
|
||||
boardState[slot.id] = cardState.card;
|
||||
renderCardOnBoard(slot, cardState.card);
|
||||
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,
|
||||
});
|
||||
console.log(`[1v1] Karte gespielt: ${cardState.card.name} -> ${slot.id}`);
|
||||
});
|
||||
});
|
||||
|
||||
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; }
|
||||
boardState[data.boardSlot] = data.card;
|
||||
renderCardOnBoard(slotEl, data.card);
|
||||
console.log('[1v1] Gegner Karte:', data.card?.name, '->', data.boardSlot);
|
||||
});
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
AUFGEBEN
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
function closeToArena() {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.document.getElementById('arena-backdrop')?.remove();
|
||||
window.parent.document.getElementById('arena-popup')?.remove();
|
||||
} else {
|
||||
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;';
|
||||
modal.innerHTML = `
|
||||
<div style="background:linear-gradient(#2a1a08,#1a0f04);border:2px solid #c8960c;border-radius:14px;padding:32px 40px;text-align:center;max-width:360px;box-shadow:0 20px 60px rgba(0,0,0,0.9);font-family:'Cinzel',serif;">
|
||||
<div style="font-size:40px;margin-bottom:12px;">\uD83C\uDFF3\uFE0F</div>
|
||||
<div style="font-size:18px;color:#f0d060;letter-spacing:3px;margin-bottom:10px;">AUFGEBEN?</div>
|
||||
<p style="color:#a08060;font-size:12px;line-height:1.7;margin-bottom:24px;">Willst du wirklich aufgeben?<br>Du erh\u00E4ltst <strong style="color:#e74c3c;">keine Punkte</strong> f\u00FCr dieses Match.</p>
|
||||
<div style="display:flex;gap:12px;justify-content:center;">
|
||||
<button id="surrender-no" style="padding:10px 24px;background:linear-gradient(#1a4a18,#0f2a0e);border:2px solid #4a8a3c;border-radius:8px;color:#a0e090;font-family:'Cinzel',serif;font-size:12px;cursor:pointer;">\u2716 Weiterk\u00E4mpfen</button>
|
||||
<button id="surrender-yes" style="padding:10px 24px;background:linear-gradient(#4a1010,#2a0808);border:2px solid #8a3030;border-radius:8px;color:#e07070;font-family:'Cinzel',serif;font-size:12px;cursor:pointer;">\uD83C\uDFF3 Aufgeben</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(modal);
|
||||
document.getElementById('surrender-no').addEventListener('click', () => modal.remove());
|
||||
document.getElementById('surrender-yes').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
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;";
|
||||
overlay.innerHTML = iWon
|
||||
? `<div style="font-size:64px;margin-bottom:20px;">\uD83C\uDFC6</div>
|
||||
<div style="font-size:34px;color:#f0d060;letter-spacing:6px;margin-bottom:12px;">SIEG!</div>
|
||||
<div style="font-size:16px;color:#a0d090;letter-spacing:2px;margin-bottom:6px;">Dein Gegner hat aufgegeben.</div>
|
||||
<div style="font-size:12px;color:#606060;margin-bottom:32px;">Du erh\u00E4ltst die Arena-Punkte.</div>
|
||||
<button id="surrender-close-btn" style="background:linear-gradient(135deg,#1a5a18,#27ae60);border:2px solid rgba(100,220,100,0.6);border-radius:10px;color:#fff;font-family:'Cinzel',serif;font-size:14px;letter-spacing:3px;padding:12px 36px;cursor:pointer;">\u2714 WEITER</button>`
|
||||
: `<div style="font-size:64px;margin-bottom:20px;opacity:0.7;">\uD83C\uDFF3\uFE0F</div>
|
||||
<div style="font-size:34px;color:#e74c3c;letter-spacing:6px;margin-bottom:12px;">AUFGEGEBEN</div>
|
||||
<div style="font-size:16px;color:#a08060;letter-spacing:2px;margin-bottom:6px;">Du hast das Match aufgegeben.</div>
|
||||
<div style="font-size:12px;color:#606060;margin-bottom:32px;">Keine Punkte f\u00FCr dieses Match.</div>
|
||||
<button id="surrender-close-btn" style="background:linear-gradient(135deg,#3a2810,#2a1a08);border:2px solid rgba(200,150,60,0.5);border-radius:10px;color:#c8a860;font-family:'Cinzel',serif;font-size:14px;letter-spacing:3px;padding:12px 36px;cursor:pointer;">\u2190 ZUR\u00DCCK ZUR ARENA</button>`;
|
||||
document.body.appendChild(overlay);
|
||||
document.getElementById('surrender-close-btn').addEventListener('click', closeToArena);
|
||||
setTimeout(closeToArena, 8000);
|
||||
}
|
||||
|
||||
socket.on('player_surrendered', data => {
|
||||
if (data.slot !== mySlot) showSurrenderMessage(true);
|
||||
});
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
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');
|
||||
if (data) updateResultWithPoints(data);
|
||||
}
|
||||
|
||||
function updateResultWithPoints(data) {
|
||||
const overlay = document.getElementById('match-result-overlay');
|
||||
const pointsEl = document.getElementById('result-points');
|
||||
const levelupEl = document.getElementById('result-levelup');
|
||||
const progressEl = document.getElementById('result-progress-wrap');
|
||||
const fillEl = document.getElementById('result-progress-fill');
|
||||
const lvlLbl = document.getElementById('result-level-label');
|
||||
const ptsLbl = document.getElementById('result-pts-label');
|
||||
|
||||
if (!overlay.classList.contains('show')) {
|
||||
document.getElementById('result-title').textContent = data.won ? '\u2694\uFE0F SIEG!' : '\uD83D\uDC80 NIEDERLAGE';
|
||||
document.getElementById('result-title').className = 'result-title ' + (data.won ? 'win' : 'lose');
|
||||
overlay.classList.add('show');
|
||||
}
|
||||
|
||||
pointsEl.textContent = data.awarded > 0
|
||||
? '+' + data.awarded + ' Arena-Punkte'
|
||||
: 'Keine Punkte (Aufgabe zu fr\u00FCh oder Tageslimit)';
|
||||
|
||||
if (data.level_up) {
|
||||
levelupEl.style.display = 'block';
|
||||
levelupEl.textContent = '\u2B06 LEVEL UP! \u2192 Level ' + data.new_level;
|
||||
}
|
||||
|
||||
fetch('/api/points/me').then(r => r.json()).then(me => {
|
||||
progressEl.style.display = 'block';
|
||||
lvlLbl.textContent = 'Level ' + me.current_level + (me.next_level ? ' \u2192 ' + me.next_level : ' (MAX)');
|
||||
ptsLbl.textContent = me.points_this_level + ' / ' + me.points_for_next + ' Pts';
|
||||
requestAnimationFrame(() => { fillEl.style.width = me.progress_percent + '%'; });
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
function closePopup() { closeToArena(); }
|
||||
|
||||
socket.on('match_result', data => updateResultWithPoints(data));
|
||||
socket.on('match_cancelled', () => closePopup());
|
||||
|
||||
document.getElementById('result-close-btn').addEventListener('click', closePopup);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
AVATAR
|
||||
═══════════════════════════════════════════════════════════ */
|
||||
function loadAvatar(input, imgId, parentId) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
const r = new FileReader();
|
||||
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');
|
||||
};
|
||||
r.readAsDataURL(file);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════
|
||||
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'); });
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user