/* ================================ Kristall-Mapping (aus carddeck.js) ================================ */ 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 = `Stufe ${rarity}`; return img.repeat(count); } function cardHTML(card, isFront = true) { if (!isFront) return `?`; // image zuerst, dann icon als Fallback const imgFile = card?.image || card?.icon || null; const img = imgFile ? `/images/cards/${imgFile}` : "/images/items/rueckseite.png"; return ` ${card?.name || ${card?.attack != null ? `${card.attack}` : ""} ${card?.defends != null ? `${card.defends}` : ""} ${card?.cooldown != null ? `${card.cooldown}` : ""} ${card?.rarity ? `
${rarityImgs(card.rarity, 11)}
` : ""}
${card?.name || ""}
`; } /* ================================ 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 = `
${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 `
${ev.label} ${done ? `
` : ""} ${locked ? `
${costLabel}
benötigt
` : ""}
${ev.label}
`; }) .join("")}
Inhalt folgt...
`; 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", `
`, ); } if (!card.querySelector(".event-done-label")) { card.insertAdjacentHTML( "beforeend", `Bereits erledigt`, ); } } } 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})…
Suche abbrechen`); 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}
Suche abbrechen`); 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 = `
⚔️ Match gefunden!
${myName}  vs  ${opponentName}
`; 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 = `
⚔️ ${mode} · vs ${opponentName} ${matchId}
`; 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 = `⚠️ ${data.name} hat das Team verlassen.`; }); socket.on("2v2_searching", () => { const s = body.querySelector("#daily-team-status"); if (s) s.innerHTML = `
⏳ Suche nach Gegnerteam…
`; 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 = `
Keine offenen Teams vorhanden.
`; return; } el.innerHTML = list.map(t => `
⚔️ ${t.leader} Lvl ${t.leaderLevel} ${t.count}/2
`).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 => `
${p.name} Lvl ${p.level} ${p.ready ? '✅ Bereit' : '⌛ Wartet'}
`).join(""); if (data.count < 2) { actionsEl.innerHTML = `
Warte auf Partner…
`; } else { const myEntry = data.players.find(p => p.name === daily2v2Me?.name); const iAmReady = myEntry?.ready; actionsEl.innerHTML = iAmReady ? `` : ``; 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 = `${msg}`; 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 = `?`; 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 = `?`; 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 = `?`; 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); }); }