/* ================================ 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/runenhaufen.png", label: "Textzeile 2" }, { id: 3, img: "/images/items/runenhaufen.png", label: "Textzeile 3" }, { id: 4, img: "/images/items/runenhaufen.png", label: "Textzeile 4" }, { id: 5, img: "/images/items/runenhaufen.png", label: "Textzeile 5" }, ]; // Täglichen Status vom Server laden let completedToday = []; try { const statusRes = await fetch("/api/daily/status"); if (statusRes.ok) { const statusData = await statusRes.json(); completedToday = statusData.completed || []; } } catch (e) { console.error("Daily-Status laden fehlgeschlagen", e); } body.innerHTML = `
${events.map(ev => { const done = completedToday.includes(ev.id); return `
${ev.label} ${done ? `
` : ''}
${ev.label} ${done ? `Bereits erledigt` : ''}
`; }).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 eventsGrid = body.querySelector("#events-grid"); /* ── Event-Karten ── */ body.querySelectorAll(".event-card").forEach(card => { card.addEventListener("click", () => { // Bereits erledigt → nicht anklickbar if (card.dataset.done === "true") return; if (card.dataset.type === "booster") { eventsGrid.style.display = "none"; boosterUi.style.display = "flex"; resetBooster(); 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 || !allRevealed) return; // ← gesperrt bis alle Karten enthüllt eventsGrid.style.display = ""; boosterUi.style.display = "none"; clearAllIntervals(); isSpinning = false; }); document.addEventListener("keydown", e => { if (e.key === "Escape") { // Booster aktiv & noch nicht fertig → ESC komplett blockieren if (boosterUi.style.display !== "none" && (!allRevealed || isSpinning)) { e.stopPropagation(); e.preventDefault(); return; } overlay.classList.remove("active"); if (!isSpinning && allRevealed) { eventsGrid.style.display = ""; boosterUi.style.display = "none"; clearAllIntervals(); } } }); /* ── 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; window.__boosterLocked = 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; window.__boosterLocked = true; // äußere Modals sperren 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; window.__boosterLocked = false; // äußere Modals wieder freigeben 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(); }