/* ================================
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 = `
`;
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?.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 `

${done ? `
` : ""}
${locked ? `
${costLabel}
benötigt
` : ""}
${ev.label}
`;
})
.join("")}
Klicken zum Öffnen
${Array.from(
{ length: 5 },
(_, i) => `
`,
).join("")}
Bitte Spenden
100 Holz spenden
Bitte Spenden
100 Gold spenden
🗡️
1v1
Einzelkampf – Beweis deine Stärke im Duell
⚔️ 2v2 Team-Lobby
Offene Teams
Keine offenen Teams vorhanden.
`;
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);
});
}