dok/public/js/quickmenu/events.js
2026-04-07 15:15:26 +01:00

930 lines
32 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ================================
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 = `<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>` : ""}
<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" },
{
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/gold.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>
<!-- 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 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 === "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})…`,
);
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" : "#000000";
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} &nbsp;vs&nbsp; ${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) {
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);">⚔️ 1v1 · 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);
}
/* ── 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);
});
}