1191 lines
41 KiB
JavaScript
1191 lines
41 KiB
JavaScript
/* ================================
|
||
Kristall-Mapping (aus carddeck.js)
|
||
================================ */
|
||
/* ================================
|
||
Stat-Icons SVG
|
||
================================ */
|
||
const BS_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 BS_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>`;
|
||
|
||
const RARITY_CRYSTALS = {
|
||
1: "roter-cristal.png",
|
||
2: "blauer-cristal.png",
|
||
3: "gelber-cristal.png",
|
||
4: "gruener-cristal.png",
|
||
5: "oranger-cristal.png",
|
||
6: "violet-cristal.png",
|
||
7: "pinker-cristal.png",
|
||
};
|
||
|
||
function rarityImgs(rarity, size = 13) {
|
||
const file = RARITY_CRYSTALS[String(rarity)];
|
||
if (!file) return "";
|
||
const count = parseInt(rarity) || 0;
|
||
const img = `<img src="/images/items/${file}" alt="Stufe ${rarity}" style="width:${size}px;height:${size}px;object-fit:contain;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.8));">`;
|
||
return img.repeat(count);
|
||
}
|
||
|
||
function cardHTML(card, isFront = true) {
|
||
if (!isFront)
|
||
return `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
|
||
// image zuerst, dann icon als Fallback
|
||
const imgFile = card?.image || card?.icon || null;
|
||
const img = imgFile
|
||
? `/images/cards/${imgFile}`
|
||
: "/images/items/rueckseite.png";
|
||
return `
|
||
<img class="booster-slot-img" src="${img}" alt="${card?.name || ""}" draggable="false"
|
||
onerror="this.src='/images/items/rueckseite.png'">
|
||
${card?.attack != null ? `<span class="bs-stat-atk">${card.attack}</span>` : ""}
|
||
${card?.defends != null ? `<span class="bs-stat-def">${card.defends}</span>` : ""}
|
||
${card?.cooldown != null ? `<span class="bs-stat-cd">${card.cooldown}</span>` : ""}
|
||
${card?.rarity ? `<div class="bs-rarity">${rarityImgs(card.rarity, 11)}</div>` : ""}
|
||
${
|
||
card?.range != null || card?.race != null
|
||
? `
|
||
<div class="bs-range-race">
|
||
${card?.range != null ? `<span class="bs-badge-range">${BS_SVG_RANGE} ${card.range}</span>` : ""}
|
||
${card?.race != null ? `<span class="bs-badge-race">${BS_SVG_RACE} ${card.race}</span>` : ""}
|
||
</div>`
|
||
: ""
|
||
}
|
||
<div class="bs-card-name">${card?.name || ""}</div>
|
||
`;
|
||
}
|
||
|
||
/* ================================
|
||
Haupt-Export
|
||
================================ */
|
||
export async function loadEvents() {
|
||
const body = document.getElementById("qm-body-events");
|
||
if (!body) return;
|
||
|
||
if (!document.querySelector('link[href="/css/events.css"]')) {
|
||
const link = document.createElement("link");
|
||
link.rel = "stylesheet";
|
||
link.href = "/css/events.css";
|
||
document.head.appendChild(link);
|
||
}
|
||
|
||
const events = [
|
||
{
|
||
id: 1,
|
||
img: "/images/items/runenhaufen.png",
|
||
label: "Booster Öffnen",
|
||
type: "booster",
|
||
},
|
||
{ id: 2, img: "/images/items/1v1.png", label: "1 v 1", type: "arena" },
|
||
{ id: 3, img: "/images/items/2v2.png", label: "2 v 2", type: "arena2" },
|
||
{
|
||
id: 4,
|
||
img: "/images/items/holz.png",
|
||
label: "Holz Spenden",
|
||
type: "wood",
|
||
woodCost: 100,
|
||
},
|
||
{
|
||
id: 5,
|
||
img: "/images/items/gold.png",
|
||
label: "Gold Spenden",
|
||
type: "gold",
|
||
goldCost: 100,
|
||
},
|
||
];
|
||
|
||
// Täglichen Status + Holz-Bestand parallel laden
|
||
let completedToday = [];
|
||
let playerWood = 0;
|
||
let playerGold = 0;
|
||
try {
|
||
const [statusRes, hudRes] = await Promise.all([
|
||
fetch("/api/daily/status"),
|
||
fetch("/api/hud"),
|
||
]);
|
||
if (statusRes.ok) completedToday = (await statusRes.json()).completed || [];
|
||
if (hudRes.ok) {
|
||
const hud = await hudRes.json();
|
||
playerWood = hud.wood || 0;
|
||
playerGold = hud.gold || 0;
|
||
}
|
||
} catch (e) {
|
||
console.error("Laden fehlgeschlagen", e);
|
||
}
|
||
|
||
body.innerHTML = `
|
||
<div class="events-grid" id="events-grid">
|
||
${events
|
||
.map((ev) => {
|
||
const done = completedToday.includes(ev.id);
|
||
const locked =
|
||
!done &&
|
||
((ev.woodCost && playerWood < ev.woodCost) ||
|
||
(ev.goldCost && playerGold < ev.goldCost));
|
||
const costLabel = ev.woodCost
|
||
? `🪵 ${ev.woodCost} Holz`
|
||
: ev.goldCost
|
||
? `🪙 ${ev.goldCost} Gold`
|
||
: "";
|
||
const cls = done ? " event-done" : locked ? " event-locked" : "";
|
||
return `
|
||
<div class="event-card${cls}" data-event-id="${ev.id}" data-type="${ev.type || ""}" data-done="${done}" data-locked="${locked}">
|
||
<div class="event-card-img-wrap">
|
||
<img src="${ev.img}" alt="${ev.label}" draggable="false">
|
||
${done ? `<div class="event-done-overlay"></div>` : ""}
|
||
${locked ? `<div class="event-locked-overlay"><span>${costLabel}<br>benötigt</span></div>` : ""}
|
||
</div>
|
||
<span class="event-card-label">${ev.label}</span>
|
||
</div>`;
|
||
})
|
||
.join("")}
|
||
</div>
|
||
|
||
<!-- Booster UI -->
|
||
<div id="booster-ui" class="booster-ui" style="display:none;">
|
||
<button class="booster-back-btn" id="booster-back-btn">← Zurück</button>
|
||
<div class="booster-stage">
|
||
<div class="booster-left">
|
||
<img id="booster-stapel" src="/images/items/boosterstapel.png" alt="Booster" draggable="false" class="booster-stapel-img">
|
||
<span class="booster-stapel-hint" id="booster-hint">Klicken zum Öffnen</span>
|
||
</div>
|
||
<div class="booster-slots" id="booster-slots">
|
||
${Array.from(
|
||
{ length: 5 },
|
||
(_, i) => `
|
||
<div class="booster-slot" id="booster-slot-${i}">
|
||
<div class="booster-slot-inner">
|
||
<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">
|
||
</div>
|
||
</div>`,
|
||
).join("")}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Holz-Spenden UI -->
|
||
<div id="wood-ui" class="booster-ui" style="display:none;">
|
||
<button class="booster-back-btn" id="wood-back-btn">← Zurück</button>
|
||
<div class="booster-stage">
|
||
<div class="booster-left">
|
||
<div class="wood-btn-wrap">
|
||
<img id="wood-btn" src="/images/items/holz.png" alt="Holz Spenden" draggable="false" class="booster-stapel-img">
|
||
<div class="wood-stamp" id="wood-stamp">Bitte Spenden</div>
|
||
</div>
|
||
<span class="booster-stapel-hint" id="wood-hint">100 Holz spenden</span>
|
||
</div>
|
||
<div class="booster-slots">
|
||
<div class="booster-slot" id="wood-slot-0">
|
||
<div class="booster-slot-inner">
|
||
<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Gold-Spenden UI -->
|
||
<div id="gold-ui" class="booster-ui" style="display:none;">
|
||
<button class="booster-back-btn" id="gold-back-btn">← Zurück</button>
|
||
<div class="booster-stage">
|
||
<div class="booster-left">
|
||
<div class="wood-btn-wrap">
|
||
<img id="gold-btn" src="/images/items/goldmuenze.png" alt="Gold Spenden" draggable="false" class="booster-stapel-img">
|
||
<div class="gold-stamp" id="gold-stamp">Bitte Spenden</div>
|
||
</div>
|
||
<span class="booster-stapel-hint" id="gold-hint">100 Gold spenden</span>
|
||
</div>
|
||
<div class="booster-slots">
|
||
<div class="booster-slot" id="gold-slot-0">
|
||
<div class="booster-slot-inner">
|
||
<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Arena Daily UI (nur 1v1) -->
|
||
<div id="arena-ui" class="booster-ui" style="display:none;">
|
||
<button class="booster-back-btn" id="arena-back-btn">← Zurück</button>
|
||
<div class="arena-daily-wrap">
|
||
<div class="arena-mode-card-daily" id="arena-1v1-card">
|
||
<div class="arena-mode-icon">🗡️</div>
|
||
<div class="arena-mode-label-daily">1v1</div>
|
||
<div class="arena-mode-desc-daily">Einzelkampf – Beweis deine Stärke im Duell</div>
|
||
</div>
|
||
<div id="arena-daily-status" style="display:none;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Arena Daily UI (2v2 Lobby) -->
|
||
<div id="arena2-ui" class="booster-ui" style="display:none; flex-direction:column; gap:14px; padding:16px; overflow-y:auto;">
|
||
<div style="display:flex; align-items:center; gap:14px;">
|
||
<button class="booster-back-btn" id="arena2-back-btn">← Zurück</button>
|
||
<span style="font-family:'Cinzel',serif; font-size:14px; color:#f0d060;">⚔️ 2v2 Team-Lobby</span>
|
||
</div>
|
||
|
||
<!-- Team-Panel -->
|
||
<div id="daily-team-panel" style="display:none;">
|
||
<div class="arena-team-box">
|
||
<div class="arena-team-title">Dein Team</div>
|
||
<div id="daily-team-players"></div>
|
||
<div id="daily-team-actions"></div>
|
||
</div>
|
||
<div id="daily-team-status" style="margin-top:8px;"></div>
|
||
</div>
|
||
|
||
<!-- Lobby-Liste -->
|
||
<div id="daily-lobby-section">
|
||
<div class="arena-lobby-actions">
|
||
<button class="arena-btn-create" id="daily-create-team-btn">+ Eigenes Team erstellen</button>
|
||
</div>
|
||
<div class="arena-lobby-title">Offene Teams</div>
|
||
<div id="daily-lobby-list"><div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Standard Detail-Popup -->
|
||
<div id="event-detail-overlay">
|
||
<div id="event-detail-popup">
|
||
<button class="edp-close" id="edp-close-btn">✕</button>
|
||
<img class="edp-img" id="edp-img" src="" alt="">
|
||
<div class="edp-title" id="edp-title"></div>
|
||
<div class="edp-body" id="edp-body">Inhalt folgt...</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
const overlay = body.querySelector("#event-detail-overlay");
|
||
const edpImg = body.querySelector("#edp-img");
|
||
const edpTitle = body.querySelector("#edp-title");
|
||
const edpBody = body.querySelector("#edp-body");
|
||
const boosterUi = body.querySelector("#booster-ui");
|
||
const woodUi = body.querySelector("#wood-ui");
|
||
const goldUi = body.querySelector("#gold-ui");
|
||
const arenaUi = body.querySelector("#arena-ui");
|
||
const arena2Ui = body.querySelector("#arena2-ui");
|
||
const eventsGrid = body.querySelector("#events-grid");
|
||
|
||
/* ── Event-Karten ── */
|
||
body.querySelectorAll(".event-card").forEach((card) => {
|
||
card.addEventListener("click", () => {
|
||
if (card.dataset.done === "true") return;
|
||
if (card.dataset.locked === "true") return;
|
||
|
||
if (card.dataset.type === "booster") {
|
||
eventsGrid.style.display = "none";
|
||
boosterUi.style.display = "flex";
|
||
resetBooster();
|
||
return;
|
||
}
|
||
if (card.dataset.type === "arena") {
|
||
eventsGrid.style.display = "none";
|
||
arenaUi.style.display = "flex";
|
||
resetArenaDaily();
|
||
return;
|
||
}
|
||
if (card.dataset.type === "arena2") {
|
||
eventsGrid.style.display = "none";
|
||
arena2Ui.style.display = "flex";
|
||
daily2v2Leave();
|
||
open2v2DailyLobby();
|
||
return;
|
||
}
|
||
if (card.dataset.type === "gold") {
|
||
eventsGrid.style.display = "none";
|
||
goldUi.style.display = "flex";
|
||
resetGold();
|
||
return;
|
||
}
|
||
if (card.dataset.type === "wood") {
|
||
eventsGrid.style.display = "none";
|
||
woodUi.style.display = "flex";
|
||
resetWood();
|
||
return;
|
||
}
|
||
const id = Number(card.dataset.eventId);
|
||
const ev = events.find((e) => e.id === id);
|
||
if (!ev) return;
|
||
edpImg.src = ev.img;
|
||
edpImg.alt = ev.label;
|
||
edpTitle.textContent = ev.label;
|
||
edpBody.textContent = "Inhalt folgt...";
|
||
overlay.classList.add("active");
|
||
});
|
||
});
|
||
|
||
/* ── Daily als erledigt markieren (für nicht-Booster Events) ── */
|
||
async function markDailyComplete(eventId) {
|
||
try {
|
||
await fetch("/api/daily/complete", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ eventId }),
|
||
});
|
||
// Karte visuell als erledigt markieren
|
||
const card = body.querySelector(
|
||
`.event-card[data-event-id="${eventId}"]`,
|
||
);
|
||
if (card) {
|
||
card.dataset.done = "true";
|
||
card.classList.add("event-done");
|
||
const wrap = card.querySelector(".event-card-img-wrap");
|
||
if (wrap && !wrap.querySelector(".event-done-overlay")) {
|
||
wrap.insertAdjacentHTML(
|
||
"beforeend",
|
||
`<div class="event-done-overlay">✓</div>`,
|
||
);
|
||
}
|
||
if (!card.querySelector(".event-done-label")) {
|
||
card.insertAdjacentHTML(
|
||
"beforeend",
|
||
`<span class="event-done-label">Bereits erledigt</span>`,
|
||
);
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error("Daily markieren fehlgeschlagen", e);
|
||
}
|
||
}
|
||
|
||
body
|
||
.querySelector("#edp-close-btn")
|
||
.addEventListener("click", () => overlay.classList.remove("active"));
|
||
overlay.addEventListener("click", (e) => {
|
||
if (e.target === overlay) overlay.classList.remove("active");
|
||
});
|
||
|
||
body.querySelector("#booster-back-btn").addEventListener("click", () => {
|
||
if (isSpinning) return;
|
||
const boosterCard = body.querySelector('.event-card[data-type="booster"]');
|
||
const boosterDone = boosterCard?.dataset.done === "true";
|
||
if (!allRevealed && !boosterDone) return;
|
||
eventsGrid.style.display = "";
|
||
boosterUi.style.display = "none";
|
||
clearAllIntervals();
|
||
isSpinning = false;
|
||
});
|
||
|
||
body.querySelector("#wood-back-btn").addEventListener("click", () => {
|
||
if (isWoodSpinning) return;
|
||
const woodCard = body.querySelector('.event-card[data-type="wood"]');
|
||
const woodDone = woodCard?.dataset.done === "true";
|
||
if (!woodRevealed && !woodDone) return;
|
||
eventsGrid.style.display = "";
|
||
woodUi.style.display = "none";
|
||
clearWoodIntervals();
|
||
isWoodSpinning = false;
|
||
});
|
||
|
||
body.querySelector("#gold-back-btn").addEventListener("click", () => {
|
||
if (isGoldSpinning) return;
|
||
const goldCard = body.querySelector('.event-card[data-type="gold"]');
|
||
const goldDone = goldCard?.dataset.done === "true";
|
||
if (!goldRevealed && !goldDone) return;
|
||
eventsGrid.style.display = "";
|
||
goldUi.style.display = "none";
|
||
clearGoldIntervals();
|
||
isGoldSpinning = false;
|
||
});
|
||
|
||
body.querySelector("#arena-back-btn").addEventListener("click", () => {
|
||
if (isArenaSearching) return; // Während Suche gesperrt
|
||
eventsGrid.style.display = "";
|
||
arenaUi.style.display = "none";
|
||
cancelArenaSearch();
|
||
});
|
||
|
||
// ESC wird zentral in quickmenu.js behandelt (verhindert Listener-Stapelung)
|
||
|
||
/* ── Arena Daily Zustand ── */
|
||
let isArenaSearching = false;
|
||
|
||
function resetArenaDaily() {
|
||
isArenaSearching = false;
|
||
const card = body.querySelector("#arena-1v1-card");
|
||
const status = body.querySelector("#arena-daily-status");
|
||
if (card) {
|
||
card.classList.remove("searching");
|
||
card.querySelector(".arena-mode-label-daily").textContent = "1v1";
|
||
card.querySelector(".arena-mode-desc-daily").textContent =
|
||
"Einzelkampf – Beweis deine Stärke im Duell";
|
||
}
|
||
if (status) status.style.display = "none";
|
||
|
||
const backBtn = body.querySelector("#arena-back-btn");
|
||
if (backBtn) {
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
}
|
||
}
|
||
|
||
function cancelArenaSearch() {
|
||
const socket = window._socket;
|
||
if (socket) {
|
||
socket.emit("leave_1v1");
|
||
socket.off("match_found");
|
||
socket.off("queue_status");
|
||
}
|
||
resetArenaDaily();
|
||
}
|
||
|
||
body.querySelector("#arena-1v1-card").addEventListener("click", async () => {
|
||
if (isArenaSearching) return;
|
||
|
||
const socket = window._socket;
|
||
if (!socket) {
|
||
showArenaStatus("❌ Keine Verbindung zum Server.", true);
|
||
return;
|
||
}
|
||
|
||
// Spielerdaten laden
|
||
let me;
|
||
try {
|
||
const res = await fetch("/arena/me");
|
||
if (!res.ok) throw new Error(res.status);
|
||
me = await res.json();
|
||
} catch {
|
||
showArenaStatus("❌ Spielerdaten konnten nicht geladen werden.", true);
|
||
return;
|
||
}
|
||
|
||
isArenaSearching = true;
|
||
const card1v1 = body.querySelector("#arena-1v1-card");
|
||
card1v1.classList.add("searching");
|
||
card1v1.querySelector(".arena-mode-label-daily").textContent = "⏳ Suche…";
|
||
card1v1.querySelector(".arena-mode-desc-daily").textContent =
|
||
"Warte auf passenden Gegner…";
|
||
|
||
// Zurück-Button sperren während Suche
|
||
const backBtn = body.querySelector("#arena-back-btn");
|
||
backBtn.style.opacity = "0.35";
|
||
backBtn.style.cursor = "not-allowed";
|
||
|
||
showArenaStatus(`⏳ Suche Gegner (Level ${Math.max(1, me.level - 5)}–${me.level + 5})…
|
||
<br><span class="arena-cancel-link" id="arena-cancel-link">Suche abbrechen</span>`);
|
||
body
|
||
.querySelector("#arena-cancel-link")
|
||
?.addEventListener("click", () => cancelArenaSearch());
|
||
|
||
socket.off("match_found");
|
||
socket.off("queue_status");
|
||
|
||
socket.on("queue_status", (data) => {
|
||
if (data.status === "waiting") {
|
||
const pool = data.poolSize ? ` · ${data.poolSize} im Pool` : "";
|
||
showArenaStatus(`⏳ Suche Gegner (Level ${Math.max(1, me.level - 5)}–${me.level + 5})${pool}
|
||
<br><span class="arena-cancel-link" id="arena-cancel-link">Suche abbrechen</span>`);
|
||
body
|
||
.querySelector("#arena-cancel-link")
|
||
?.addEventListener("click", () => {
|
||
cancelArenaSearch();
|
||
});
|
||
}
|
||
});
|
||
|
||
socket.once("match_found", (data) => {
|
||
socket.off("queue_status");
|
||
isArenaSearching = false;
|
||
|
||
// Daily markieren
|
||
markDailyComplete(2);
|
||
|
||
// Match-Overlay + Popup öffnen (aus arena.js Logik)
|
||
showArenaMatchFound(me.name, data.opponent.name, () => {
|
||
eventsGrid.style.display = "";
|
||
arenaUi.style.display = "none";
|
||
openArenaMatchPopup(
|
||
`/arena/1v1?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
|
||
data.opponent.name,
|
||
data.matchId,
|
||
);
|
||
});
|
||
});
|
||
|
||
socket.emit("join_1v1", { id: me.id, name: me.name, level: me.level });
|
||
});
|
||
|
||
function showArenaStatus(html, isError = false) {
|
||
const box = body.querySelector("#arena-daily-status");
|
||
if (!box) return;
|
||
box.style.display = "block";
|
||
box.style.color = isError ? "#e74c3c" : "#dceb15";
|
||
box.innerHTML = html;
|
||
if (isError)
|
||
setTimeout(() => {
|
||
box.style.display = "none";
|
||
}, 3000);
|
||
}
|
||
|
||
function showArenaMatchFound(myName, opponentName, onDone) {
|
||
if (document.getElementById("match-found-overlay")) return;
|
||
const overlay = document.createElement("div");
|
||
overlay.id = "match-found-overlay";
|
||
overlay.style.cssText =
|
||
"position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.9);display:flex;flex-direction:column;align-items:center;justify-content:center;";
|
||
overlay.innerHTML = `
|
||
<div style="font-family:'Cinzel',serif;font-size:36px;color:#ffd750;letter-spacing:6px;margin-bottom:12px;">⚔️ Match gefunden!</div>
|
||
<div style="font-family:'Cinzel',serif;font-size:18px;color:rgba(255,255,255,0.75);letter-spacing:3px;">${myName} vs ${opponentName}</div>
|
||
<div style="width:300px;height:4px;background:rgba(255,215,80,0.2);border-radius:2px;margin-top:24px;overflow:hidden;">
|
||
<div id="mfBar" style="height:100%;background:#ffd750;width:0%;border-radius:2px;transition:width 1.5s ease;"></div>
|
||
</div>`;
|
||
document.body.appendChild(overlay);
|
||
requestAnimationFrame(() => {
|
||
const b = document.getElementById("mfBar");
|
||
if (b) b.style.width = "100%";
|
||
});
|
||
setTimeout(() => {
|
||
overlay.remove();
|
||
onDone();
|
||
}, 1600);
|
||
}
|
||
|
||
function openArenaMatchPopup(src, opponentName, matchId, mode = "1v1") {
|
||
document.getElementById("arena-backdrop")?.remove();
|
||
document.getElementById("arena-popup")?.remove();
|
||
const backdrop = document.createElement("div");
|
||
backdrop.id = "arena-backdrop";
|
||
backdrop.style.cssText =
|
||
"position:fixed;inset:0;background:rgba(0,0,0,0.82);backdrop-filter:blur(5px);z-index:9998;";
|
||
const popup = document.createElement("div");
|
||
popup.id = "arena-popup";
|
||
popup.style.cssText =
|
||
"position:fixed;inset:50px;z-index:9999;display:flex;flex-direction:column;border-radius:14px;overflow:hidden;box-shadow:0 0 0 1px rgba(255,215,80,0.35),0 30px 90px rgba(0,0,0,0.85);";
|
||
popup.innerHTML = `
|
||
<div style="display:flex;align-items:center;justify-content:space-between;background:rgba(10,8,5,0.95);border-bottom:1px solid rgba(255,215,80,0.3);padding:0 16px;height:42px;flex-shrink:0;">
|
||
<span style="font-family:'Cinzel',serif;font-size:13px;letter-spacing:4px;color:rgba(255,215,80,0.85);">⚔️ ${mode} · vs ${opponentName}</span>
|
||
<span style="font-size:11px;color:rgba(255,255,255,0.22);">${matchId}</span>
|
||
</div>
|
||
<iframe src="${src}" style="flex:1;border:none;width:100%;display:block;" allowfullscreen></iframe>`;
|
||
document.body.appendChild(backdrop);
|
||
document.body.appendChild(popup);
|
||
}
|
||
|
||
body.querySelector("#arena2-back-btn").addEventListener("click", () => {
|
||
if (isArena2InMatch) return;
|
||
eventsGrid.style.display = "";
|
||
arena2Ui.style.display = "none";
|
||
daily2v2Leave();
|
||
});
|
||
|
||
/* ── Arena2 Daily Zustand (2v2 Lobby) ── */
|
||
let isArena2InMatch = false;
|
||
let daily2v2TeamId = null;
|
||
let daily2v2Me = null;
|
||
|
||
async function open2v2DailyLobby() {
|
||
const socket = window._socket;
|
||
if (!socket) {
|
||
showDaily2v2Error("❌ Keine Verbindung zum Server.");
|
||
return;
|
||
}
|
||
|
||
if (!daily2v2Me) {
|
||
try {
|
||
const res = await fetch("/arena/me");
|
||
if (!res.ok) throw new Error(res.status);
|
||
daily2v2Me = await res.json();
|
||
} catch {
|
||
showDaily2v2Error("❌ Spielerdaten konnten nicht geladen werden.");
|
||
return;
|
||
}
|
||
}
|
||
|
||
socket.off("2v2_lobbies");
|
||
socket.off("2v2_team_joined");
|
||
socket.off("2v2_team_update");
|
||
socket.off("2v2_partner_left");
|
||
socket.off("2v2_searching");
|
||
socket.off("match_found_2v2");
|
||
socket.off("2v2_error");
|
||
|
||
socket.emit("get_2v2_lobbies");
|
||
socket.on("2v2_lobbies", (list) => renderDaily2v2Lobbies(list, socket));
|
||
|
||
socket.on("2v2_team_joined", (data) => {
|
||
daily2v2TeamId = data.teamId;
|
||
body.querySelector("#daily-team-panel").style.display = "block";
|
||
body.querySelector("#daily-lobby-section").style.display = "none";
|
||
});
|
||
|
||
socket.on("2v2_team_update", (data) => renderDaily2v2Team(data, socket));
|
||
|
||
socket.on("2v2_partner_left", (data) => {
|
||
const s = body.querySelector("#daily-team-status");
|
||
if (s)
|
||
s.innerHTML = `<span style="color:#e74c3c;">⚠️ ${data.name} hat das Team verlassen.</span>`;
|
||
});
|
||
|
||
socket.on("2v2_searching", () => {
|
||
const s = body.querySelector("#daily-team-status");
|
||
if (s)
|
||
s.innerHTML = `<div class="arena-searching-box">⏳ Suche nach Gegnerteam…</div>`;
|
||
const a = body.querySelector("#daily-team-actions");
|
||
if (a) a.innerHTML = "";
|
||
// Zurück sperren während Suche
|
||
const backBtn = body.querySelector("#arena2-back-btn");
|
||
if (backBtn) {
|
||
backBtn.style.opacity = "0.35";
|
||
backBtn.style.cursor = "not-allowed";
|
||
}
|
||
isArena2InMatch = true;
|
||
});
|
||
|
||
socket.once("match_found_2v2", (data) => {
|
||
socket.off("2v2_lobbies");
|
||
socket.off("2v2_team_update");
|
||
socket.off("2v2_partner_left");
|
||
socket.off("2v2_searching");
|
||
isArena2InMatch = false;
|
||
markDailyComplete(3);
|
||
|
||
showArenaMatchFound(
|
||
daily2v2Me.name,
|
||
`Team ${data.myTeam === 1 ? 2 : 1}`,
|
||
() => {
|
||
eventsGrid.style.display = "";
|
||
arena2Ui.style.display = "none";
|
||
openArenaMatchPopup(
|
||
`/arena/2v2?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,
|
||
data.opponents?.join(" & ") || "Gegner",
|
||
data.matchId,
|
||
"2v2",
|
||
);
|
||
},
|
||
);
|
||
});
|
||
|
||
socket.on("2v2_error", (data) => showDaily2v2Error(`❌ ${data.message}`));
|
||
|
||
// Buttons
|
||
body.querySelector("#daily-create-team-btn").onclick = () => {
|
||
socket.emit("create_2v2_team", daily2v2Me);
|
||
};
|
||
}
|
||
|
||
function renderDaily2v2Lobbies(list, socket) {
|
||
const el = body.querySelector("#daily-lobby-list");
|
||
if (!el) return;
|
||
if (!list.length) {
|
||
el.innerHTML = `<div class="arena-lobby-empty">Keine offenen Teams vorhanden.</div>`;
|
||
return;
|
||
}
|
||
el.innerHTML = list
|
||
.map(
|
||
(t) => `
|
||
<div class="arena-lobby-row">
|
||
<div class="arena-lobby-row-info">
|
||
<span class="arena-lobby-leader">⚔️ ${t.leader}</span>
|
||
<span class="arena-lobby-level">Lvl ${t.leaderLevel}</span>
|
||
<span class="arena-lobby-count">${t.count}/2</span>
|
||
</div>
|
||
<button class="arena-btn-join" data-teamid="${t.teamId}">Beitreten</button>
|
||
</div>`,
|
||
)
|
||
.join("");
|
||
el.querySelectorAll(".arena-btn-join").forEach((btn) => {
|
||
btn.addEventListener("click", () => {
|
||
socket.emit("join_2v2_team", {
|
||
teamId: btn.dataset.teamid,
|
||
playerData: daily2v2Me,
|
||
});
|
||
});
|
||
});
|
||
}
|
||
|
||
function renderDaily2v2Team(data, socket) {
|
||
const playersEl = body.querySelector("#daily-team-players");
|
||
const actionsEl = body.querySelector("#daily-team-actions");
|
||
if (!playersEl || !actionsEl) return;
|
||
|
||
playersEl.innerHTML = data.players
|
||
.map(
|
||
(p) => `
|
||
<div class="arena-team-player ${p.ready ? "ready" : ""}">
|
||
<span class="arena-team-player-name">${p.name}</span>
|
||
<span class="arena-team-player-level">Lvl ${p.level}</span>
|
||
<span class="arena-team-player-status">${p.ready ? "✅ Bereit" : "⌛ Wartet"}</span>
|
||
</div>`,
|
||
)
|
||
.join("");
|
||
|
||
if (data.count < 2) {
|
||
actionsEl.innerHTML = `<div class="arena-waiting-partner">Warte auf Partner…</div>`;
|
||
} else {
|
||
const myEntry = data.players.find((p) => p.name === daily2v2Me?.name);
|
||
const iAmReady = myEntry?.ready;
|
||
actionsEl.innerHTML = iAmReady
|
||
? `<button class="arena-btn-ready active" disabled>✅ Du bist bereit</button>`
|
||
: `<button class="arena-btn-ready" id="daily-ready-btn">⚔️ Bereit</button>`;
|
||
if (!iAmReady) {
|
||
body
|
||
.querySelector("#daily-ready-btn")
|
||
?.addEventListener("click", () => {
|
||
socket.emit("2v2_player_ready", { teamId: daily2v2TeamId });
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
function daily2v2Leave() {
|
||
const socket = window._socket;
|
||
if (socket) {
|
||
socket.emit("leave_2v2_team");
|
||
socket.off("2v2_lobbies");
|
||
socket.off("2v2_team_joined");
|
||
socket.off("2v2_team_update");
|
||
socket.off("2v2_partner_left");
|
||
socket.off("2v2_searching");
|
||
socket.off("match_found_2v2");
|
||
socket.off("2v2_error");
|
||
}
|
||
daily2v2TeamId = null;
|
||
isArena2InMatch = false;
|
||
// Reset UI
|
||
const panel = body.querySelector("#daily-team-panel");
|
||
const section = body.querySelector("#daily-lobby-section");
|
||
const backBtn = body.querySelector("#arena2-back-btn");
|
||
if (panel) panel.style.display = "none";
|
||
if (section) section.style.display = "";
|
||
if (backBtn) {
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
}
|
||
}
|
||
|
||
function showDaily2v2Error(msg) {
|
||
const s = body.querySelector("#daily-team-status");
|
||
if (!s) return;
|
||
s.innerHTML = `<span style="color:#e74c3c;">${msg}</span>`;
|
||
setTimeout(() => {
|
||
s.innerHTML = "";
|
||
}, 3000);
|
||
}
|
||
|
||
// Lobby beim Öffnen des Arena2-UI initialisieren
|
||
const origArena2Click = body.querySelector('.event-card[data-type="arena2"]');
|
||
if (origArena2Click) {
|
||
origArena2Click.addEventListener("click", () => {
|
||
// open2v2DailyLobby wird nach dem UI-Wechsel in resetArena2Daily aufgerufen
|
||
});
|
||
}
|
||
|
||
/* ── Booster Zustand ── */
|
||
let allCards = [];
|
||
let isSpinning = false;
|
||
let allRevealed = false; // true sobald alle 5 Karten aufgedeckt + gespeichert
|
||
let spinIntervals = {}; // { index: intervalId }
|
||
let slotLocked = {}; // { index: true } → verhindert Überschreiben nach reveal
|
||
|
||
function clearAllIntervals() {
|
||
Object.values(spinIntervals).forEach((id) => clearInterval(id));
|
||
spinIntervals = {};
|
||
slotLocked = {};
|
||
}
|
||
|
||
async function preloadCards() {
|
||
if (allCards.length) return;
|
||
try {
|
||
const res = await fetch("/api/booster/cards");
|
||
if (!res.ok) throw new Error(res.status);
|
||
allCards = await res.json();
|
||
} catch (e) {
|
||
console.error("Karten laden fehlgeschlagen", e);
|
||
}
|
||
}
|
||
|
||
function resetBooster() {
|
||
clearAllIntervals();
|
||
isSpinning = false;
|
||
allRevealed = false;
|
||
|
||
for (let i = 0; i < 5; i++) {
|
||
const inner = body.querySelector(
|
||
`#booster-slot-${i} .booster-slot-inner`,
|
||
);
|
||
inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
body
|
||
.querySelector(`#booster-slot-${i}`)
|
||
.classList.remove("revealed", "spinning");
|
||
}
|
||
|
||
const stapel = body.querySelector("#booster-stapel");
|
||
stapel.classList.remove("used");
|
||
stapel.style.opacity = "1";
|
||
stapel.style.cursor = "pointer";
|
||
body.querySelector("#booster-hint").textContent = "Klicken zum Öffnen";
|
||
preloadCards();
|
||
}
|
||
|
||
/* ── Slot drehen – 350ms, damit man die Karten erkennt ── */
|
||
function startSpinSlot(index) {
|
||
slotLocked[index] = false;
|
||
const slot = body.querySelector(`#booster-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.add("spinning");
|
||
|
||
const iv = setInterval(() => {
|
||
if (!allCards.length || slotLocked[index]) return;
|
||
const rnd = allCards[Math.floor(Math.random() * allCards.length)];
|
||
inner.innerHTML = cardHTML(rnd, true);
|
||
}, 150);
|
||
|
||
spinIntervals[index] = iv;
|
||
}
|
||
|
||
/* ── Slot enthüllen – Sperre setzen BEVOR clearInterval ── */
|
||
function revealSlot(index, card) {
|
||
slotLocked[index] = true; // zuerst sperren
|
||
clearInterval(spinIntervals[index]); // dann stoppen
|
||
delete spinIntervals[index];
|
||
|
||
console.log(`[Booster] Slot ${index} enthüllt:`, card);
|
||
|
||
const slot = body.querySelector(`#booster-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.remove("spinning");
|
||
slot.classList.add("revealed");
|
||
|
||
// innerHTML setzen und danach nochmal sicherstellen (doppelte Absicherung)
|
||
inner.innerHTML = cardHTML(card, true);
|
||
setTimeout(() => {
|
||
if (slot.classList.contains("revealed")) {
|
||
inner.innerHTML = cardHTML(card, true);
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
/* ── Stapel klicken ── */
|
||
body.querySelector("#booster-stapel").addEventListener("click", async () => {
|
||
if (isSpinning) return;
|
||
if (!allCards.length) await preloadCards();
|
||
if (!allCards.length) return;
|
||
|
||
isSpinning = true;
|
||
|
||
const stapel = body.querySelector("#booster-stapel");
|
||
stapel.classList.add("used");
|
||
stapel.style.opacity = "0.35";
|
||
stapel.style.cursor = "default";
|
||
body.querySelector("#booster-hint").textContent = "Wird gezogen...";
|
||
|
||
// Zurück-Button während der Animation sperren
|
||
const backBtn = body.querySelector("#booster-back-btn");
|
||
backBtn.style.opacity = "0.35";
|
||
backBtn.style.cursor = "not-allowed";
|
||
|
||
let drawnCards = [];
|
||
try {
|
||
const res = await fetch("/api/booster/open", { method: "POST" });
|
||
const text = await res.text();
|
||
console.log("[Booster] API Antwort raw:", text);
|
||
const data = JSON.parse(text);
|
||
console.log("[Booster] API Antwort parsed:", data);
|
||
drawnCards = data.cards || [];
|
||
console.log("[Booster] drawnCards:", drawnCards);
|
||
} catch (e) {
|
||
console.error("Booster öffnen fehlgeschlagen", e);
|
||
resetBooster();
|
||
return;
|
||
}
|
||
|
||
for (let i = 0; i < 5; i++) startSpinSlot(i);
|
||
|
||
for (let i = 0; i < 5; i++) {
|
||
setTimeout(() => revealSlot(i, drawnCards[i]), (i + 1) * 5000);
|
||
}
|
||
|
||
setTimeout(
|
||
async () => {
|
||
body.querySelector("#booster-hint").textContent =
|
||
"Karten werden gespeichert...";
|
||
isSpinning = false;
|
||
|
||
// Karten in user_cards speichern
|
||
try {
|
||
const cardIds = drawnCards.map((c) => c.id);
|
||
const saveRes = await fetch("/api/booster/save", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ cardIds }),
|
||
});
|
||
if (!saveRes.ok) throw new Error(saveRes.status);
|
||
} catch (e) {
|
||
console.error("Karten speichern fehlgeschlagen", e);
|
||
}
|
||
|
||
// Booster-Daily als erledigt markieren (event_id = 1)
|
||
await markDailyComplete(1);
|
||
|
||
allRevealed = true;
|
||
body.querySelector("#booster-hint").textContent =
|
||
"Alle Karten erhalten! ✓";
|
||
const backBtn = body.querySelector("#booster-back-btn");
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
},
|
||
5 * 5000 + 500,
|
||
);
|
||
});
|
||
|
||
preloadCards();
|
||
|
||
/* ================================
|
||
Holz-Spenden Zustand
|
||
================================ */
|
||
let isWoodSpinning = false;
|
||
let woodRevealed = false;
|
||
let woodIntervals = {};
|
||
let woodLocked = {};
|
||
let rarity3Cards = [];
|
||
|
||
async function preloadRarity3() {
|
||
if (rarity3Cards.length) return;
|
||
try {
|
||
const res = await fetch("/api/booster/cards");
|
||
if (!res.ok) throw new Error(res.status);
|
||
const all = await res.json();
|
||
rarity3Cards = all.filter((c) => parseInt(c.rarity) === 3);
|
||
// Fallback: wenn keine rarity-3 vorhanden alle nehmen
|
||
if (!rarity3Cards.length) rarity3Cards = all;
|
||
} catch (e) {
|
||
console.error("Rarity-3 Karten laden fehlgeschlagen", e);
|
||
}
|
||
}
|
||
|
||
function clearWoodIntervals() {
|
||
Object.values(woodIntervals).forEach((id) => clearInterval(id));
|
||
woodIntervals = {};
|
||
woodLocked = {};
|
||
}
|
||
|
||
function resetWood() {
|
||
clearWoodIntervals();
|
||
isWoodSpinning = false;
|
||
woodRevealed = false;
|
||
|
||
const inner = body.querySelector(`#wood-slot-0 .booster-slot-inner`);
|
||
inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
body.querySelector(`#wood-slot-0`).classList.remove("revealed", "spinning");
|
||
|
||
const btn = body.querySelector("#wood-btn");
|
||
const stamp = body.querySelector("#wood-stamp");
|
||
btn.classList.remove("used");
|
||
btn.style.opacity = "1";
|
||
btn.style.cursor = "pointer";
|
||
if (stamp) stamp.style.display = "";
|
||
body.querySelector("#wood-hint").textContent = "100 Holz spenden";
|
||
preloadRarity3();
|
||
}
|
||
|
||
function startWoodSlot(index) {
|
||
woodLocked[index] = false;
|
||
const slot = body.querySelector(`#wood-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.add("spinning");
|
||
|
||
const iv = setInterval(() => {
|
||
if (!rarity3Cards.length || woodLocked[index]) return;
|
||
const rnd = rarity3Cards[Math.floor(Math.random() * rarity3Cards.length)];
|
||
inner.innerHTML = cardHTML(rnd, true);
|
||
}, 150);
|
||
|
||
woodIntervals[index] = iv;
|
||
}
|
||
|
||
function revealWoodSlot(index, card) {
|
||
woodLocked[index] = true;
|
||
clearInterval(woodIntervals[index]);
|
||
delete woodIntervals[index];
|
||
|
||
const slot = body.querySelector(`#wood-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.remove("spinning");
|
||
slot.classList.add("revealed");
|
||
inner.innerHTML = cardHTML(card, true);
|
||
setTimeout(() => {
|
||
if (slot.classList.contains("revealed"))
|
||
inner.innerHTML = cardHTML(card, true);
|
||
}, 100);
|
||
}
|
||
|
||
body.querySelector("#wood-btn").addEventListener("click", async () => {
|
||
if (isWoodSpinning) return;
|
||
if (!rarity3Cards.length) await preloadRarity3();
|
||
if (!rarity3Cards.length) return;
|
||
|
||
isWoodSpinning = true;
|
||
|
||
const btn = body.querySelector("#wood-btn");
|
||
const stamp = body.querySelector("#wood-stamp");
|
||
btn.classList.add("used");
|
||
btn.style.opacity = "0.35";
|
||
btn.style.cursor = "default";
|
||
if (stamp) stamp.style.display = "none";
|
||
body.querySelector("#wood-hint").textContent = "Wird verarbeitet...";
|
||
|
||
const backBtn = body.querySelector("#wood-back-btn");
|
||
backBtn.style.opacity = "0.35";
|
||
backBtn.style.cursor = "not-allowed";
|
||
|
||
// API aufrufen – 100 Holz abziehen + Karte ziehen
|
||
let drawnCard = null;
|
||
try {
|
||
const res = await fetch("/api/booster/wood-donate", { method: "POST" });
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
body.querySelector("#wood-hint").textContent = data.error || "Fehler";
|
||
isWoodSpinning = false;
|
||
btn.classList.remove("used");
|
||
btn.style.opacity = "1";
|
||
btn.style.cursor = "pointer";
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
return;
|
||
}
|
||
drawnCard = data.card;
|
||
} catch (e) {
|
||
console.error("Holz-Spenden fehlgeschlagen", e);
|
||
resetWood();
|
||
return;
|
||
}
|
||
|
||
// Nur Slot 0 starten
|
||
startWoodSlot(0);
|
||
|
||
// Nach 5 Sekunden enthüllen
|
||
setTimeout(() => {
|
||
revealWoodSlot(0, drawnCard);
|
||
|
||
body.querySelector("#wood-hint").textContent = "Karte erhalten! ✓";
|
||
isWoodSpinning = false;
|
||
woodRevealed = true;
|
||
|
||
markDailyComplete(4);
|
||
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
}, 5000);
|
||
});
|
||
|
||
preloadRarity3();
|
||
|
||
/* ================================
|
||
Gold-Spenden Zustand
|
||
================================ */
|
||
let isGoldSpinning = false;
|
||
let goldRevealed = false;
|
||
let goldIntervals = {};
|
||
let goldLocked = {};
|
||
|
||
function clearGoldIntervals() {
|
||
Object.values(goldIntervals).forEach((id) => clearInterval(id));
|
||
goldIntervals = {};
|
||
goldLocked = {};
|
||
}
|
||
|
||
function resetGold() {
|
||
clearGoldIntervals();
|
||
isGoldSpinning = false;
|
||
goldRevealed = false;
|
||
|
||
const inner = body.querySelector(`#gold-slot-0 .booster-slot-inner`);
|
||
inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
|
||
body.querySelector(`#gold-slot-0`).classList.remove("revealed", "spinning");
|
||
|
||
const btn = body.querySelector("#gold-btn");
|
||
const stamp = body.querySelector("#gold-stamp");
|
||
btn.classList.remove("used");
|
||
btn.style.opacity = "1";
|
||
btn.style.cursor = "pointer";
|
||
if (stamp) stamp.style.display = "";
|
||
body.querySelector("#gold-hint").textContent = "100 Gold spenden";
|
||
preloadRarity3();
|
||
}
|
||
|
||
function startGoldSlot(index) {
|
||
goldLocked[index] = false;
|
||
const slot = body.querySelector(`#gold-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.add("spinning");
|
||
|
||
const iv = setInterval(() => {
|
||
if (!rarity3Cards.length || goldLocked[index]) return;
|
||
const rnd = rarity3Cards[Math.floor(Math.random() * rarity3Cards.length)];
|
||
inner.innerHTML = cardHTML(rnd, true);
|
||
}, 150);
|
||
|
||
goldIntervals[index] = iv;
|
||
}
|
||
|
||
function revealGoldSlot(index, card) {
|
||
goldLocked[index] = true;
|
||
clearInterval(goldIntervals[index]);
|
||
delete goldIntervals[index];
|
||
|
||
const slot = body.querySelector(`#gold-slot-${index}`);
|
||
const inner = slot.querySelector(".booster-slot-inner");
|
||
slot.classList.remove("spinning");
|
||
slot.classList.add("revealed");
|
||
inner.innerHTML = cardHTML(card, true);
|
||
setTimeout(() => {
|
||
if (slot.classList.contains("revealed"))
|
||
inner.innerHTML = cardHTML(card, true);
|
||
}, 100);
|
||
}
|
||
|
||
body.querySelector("#gold-btn").addEventListener("click", async () => {
|
||
if (isGoldSpinning) return;
|
||
if (!rarity3Cards.length) await preloadRarity3();
|
||
if (!rarity3Cards.length) return;
|
||
|
||
isGoldSpinning = true;
|
||
|
||
const btn = body.querySelector("#gold-btn");
|
||
const stamp = body.querySelector("#gold-stamp");
|
||
btn.classList.add("used");
|
||
btn.style.opacity = "0.35";
|
||
btn.style.cursor = "default";
|
||
if (stamp) stamp.style.display = "none";
|
||
body.querySelector("#gold-hint").textContent = "Wird verarbeitet...";
|
||
|
||
const backBtn = body.querySelector("#gold-back-btn");
|
||
backBtn.style.opacity = "0.35";
|
||
backBtn.style.cursor = "not-allowed";
|
||
|
||
let drawnCard = null;
|
||
try {
|
||
const res = await fetch("/api/booster/gold-donate", { method: "POST" });
|
||
const data = await res.json();
|
||
if (!res.ok) {
|
||
body.querySelector("#gold-hint").textContent = data.error || "Fehler";
|
||
isGoldSpinning = false;
|
||
btn.classList.remove("used");
|
||
btn.style.opacity = "1";
|
||
btn.style.cursor = "pointer";
|
||
if (stamp) stamp.style.display = "";
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
return;
|
||
}
|
||
drawnCard = data.card;
|
||
} catch (e) {
|
||
console.error("Gold-Spenden fehlgeschlagen", e);
|
||
resetGold();
|
||
return;
|
||
}
|
||
|
||
startGoldSlot(0);
|
||
|
||
setTimeout(() => {
|
||
revealGoldSlot(0, drawnCard);
|
||
body.querySelector("#gold-hint").textContent = "Karte erhalten! ✓";
|
||
isGoldSpinning = false;
|
||
goldRevealed = true;
|
||
markDailyComplete(5);
|
||
backBtn.style.opacity = "1";
|
||
backBtn.style.cursor = "pointer";
|
||
}, 5000);
|
||
});
|
||
}
|