This commit is contained in:
cay 2026-04-07 14:52:07 +01:00
parent 1ad2b2a25b
commit f68ff01825
2 changed files with 163 additions and 199 deletions

View File

@ -1,4 +1,5 @@
export async function loadArena() { export async function loadArena(options = {}) {
const { dailyOnly = false, onMatchComplete = null } = options;
const ui = document.querySelector(".building-ui"); const ui = document.querySelector(".building-ui");
ui.innerHTML = ` ui.innerHTML = `
@ -15,6 +16,7 @@ export async function loadArena() {
<div class="arena-mode-desc">Einzelkampf Beweis deine Stärke im Duell</div> <div class="arena-mode-desc">Einzelkampf Beweis deine Stärke im Duell</div>
</div> </div>
${!dailyOnly ? `
<div class="arena-mode-card" data-mode="2v2"> <div class="arena-mode-card" data-mode="2v2">
<div class="arena-mode-icon"></div> <div class="arena-mode-icon"></div>
<div class="arena-mode-label">2v2</div> <div class="arena-mode-label">2v2</div>
@ -26,6 +28,7 @@ export async function loadArena() {
<div class="arena-mode-label">4v4</div> <div class="arena-mode-label">4v4</div>
<div class="arena-mode-desc">Schlachtruf Führe deine Truppe zum Sieg</div> <div class="arena-mode-desc">Schlachtruf Führe deine Truppe zum Sieg</div>
</div> </div>
` : ''}
</div> </div>
@ -36,7 +39,7 @@ export async function loadArena() {
`; `;
injectArenaStyles(); injectArenaStyles();
initArenaModes(); initArenaModes({ onMatchComplete });
} }
/* ── Styles ────────────────────────────────────────────────────────────────── */ /* ── Styles ────────────────────────────────────────────────────────────────── */
@ -225,13 +228,13 @@ function getSocket() {
} }
/* ── Klick-Handler initialisieren ─────────────────────────────────────────── */ /* ── Klick-Handler initialisieren ─────────────────────────────────────────── */
function initArenaModes() { function initArenaModes({ onMatchComplete = null } = {}) {
document.querySelectorAll(".arena-mode-card").forEach((card) => { document.querySelectorAll(".arena-mode-card").forEach((card) => {
card.addEventListener("click", () => { card.addEventListener("click", () => {
const mode = card.dataset.mode; const mode = card.dataset.mode;
if (mode === "1v1") { if (mode === "1v1") {
handle1v1Click(card); handle1v1Click(card, onMatchComplete);
} else { } else {
console.log("Arena Modus gewählt:", mode); console.log("Arena Modus gewählt:", mode);
// Platzhalter für 2v2 / 4v4 // Platzhalter für 2v2 / 4v4
@ -241,7 +244,7 @@ function initArenaModes() {
} }
/* ── 1v1: Hauptlogik ───────────────────────────────────────────────────────── */ /* ── 1v1: Hauptlogik ───────────────────────────────────────────────────────── */
async function handle1v1Click(card) { async function handle1v1Click(card, onMatchComplete = null) {
const socket = getSocket(); const socket = getSocket();
if (!socket) { if (!socket) {
showArenaError("Keine Verbindung zum Server. Bitte Seite neu laden."); showArenaError("Keine Verbindung zum Server. Bitte Seite neu laden.");
@ -287,6 +290,9 @@ async function handle1v1Click(card) {
setCardSearching(card, false); setCardSearching(card, false);
hideQueueStatus(); hideQueueStatus();
// Daily als erledigt markieren
if (typeof onMatchComplete === "function") onMatchComplete();
showMatchFoundOverlay(me.name, data.opponent.name, () => { showMatchFoundOverlay(me.name, data.opponent.name, () => {
openArenaPopup( openArenaPopup(
`/arena/1v1?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`, `/arena/1v1?match=${encodeURIComponent(data.matchId)}&slot=${encodeURIComponent(data.mySlot)}`,

View File

@ -20,22 +20,19 @@ function rarityImgs(rarity, size = 13) {
} }
function cardHTML(card, isFront = true) { function cardHTML(card, isFront = true) {
if (!isFront) if (!isFront) return `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
return `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
// image zuerst, dann icon als Fallback // image zuerst, dann icon als Fallback
const imgFile = card?.image || card?.icon || null; const imgFile = card?.image || card?.icon || null;
const img = imgFile const img = imgFile ? `/images/cards/${imgFile}` : "/images/items/rueckseite.png";
? `/images/cards/${imgFile}`
: "/images/items/rueckseite.png";
return ` return `
<img class="booster-slot-img" src="${img}" alt="${card?.name || ""}" draggable="false" <img class="booster-slot-img" src="${img}" alt="${card?.name || ''}" draggable="false"
onerror="this.src='/images/items/rueckseite.png'"> onerror="this.src='/images/items/rueckseite.png'">
${card?.attack != null ? `<span class="bs-stat-atk">${card.attack}</span>` : ""} ${card?.attack != null ? `<span class="bs-stat-atk">${card.attack}</span>` : ""}
${card?.defends != null ? `<span class="bs-stat-def">${card.defends}</span>` : ""} ${card?.defends != null ? `<span class="bs-stat-def">${card.defends}</span>` : ""}
${card?.cooldown != null ? `<span class="bs-stat-cd">${card.cooldown}</span>` : ""} ${card?.cooldown!= null ? `<span class="bs-stat-cd">${card.cooldown}</span>` : ""}
${card?.rarity ? `<div class="bs-rarity">${rarityImgs(card.rarity, 11)}</div>` : ""} ${card?.rarity ? `<div class="bs-rarity">${rarityImgs(card.rarity, 11)}</div>` : ""}
<div class="bs-card-name">${card?.name || ""}</div> <div class="bs-card-name">${card?.name || ''}</div>
`; `;
} }
@ -48,40 +45,23 @@ export async function loadEvents() {
if (!document.querySelector('link[href="/css/events.css"]')) { if (!document.querySelector('link[href="/css/events.css"]')) {
const link = document.createElement("link"); const link = document.createElement("link");
link.rel = "stylesheet"; link.rel = "stylesheet";
link.href = "/css/events.css"; link.href = "/css/events.css";
document.head.appendChild(link); document.head.appendChild(link);
} }
const events = [ const events = [
{ { id: 1, img: "/images/items/runenhaufen.png", label: "Booster Öffnen", type: "booster" },
id: 1, { id: 2, img: "/images/items/runenhaufen.png", label: "1v1 Duell", type: "arena" },
img: "/images/items/runenhaufen.png", { id: 3, img: "/images/items/runenhaufen.png", label: "Textzeile 3" },
label: "Booster Öffnen", { id: 4, img: "/images/items/holz.png", label: "Holz Spenden", type: "wood", woodCost: 100 },
type: "booster", { id: 5, img: "/images/items/gold.png", label: "Gold Spenden", type: "gold", goldCost: 100 },
},
{ id: 2, img: "/images/items/1v1.png", label: "1v1" },
{ id: 3, img: "/images/items/2v2.png", label: "2v2" },
{
id: 4,
img: "/images/items/holz.png",
label: "Holz Spenden",
type: "wood",
woodCost: 100,
},
{
id: 5,
img: "/images/items/goldmuenze.png",
label: "Gold Spenden",
type: "gold",
goldCost: 100,
},
]; ];
// Täglichen Status + Holz-Bestand parallel laden // Täglichen Status + Holz-Bestand parallel laden
let completedToday = []; let completedToday = [];
let playerWood = 0; let playerWood = 0;
let playerGold = 0; let playerGold = 0;
try { try {
const [statusRes, hudRes] = await Promise.all([ const [statusRes, hudRes] = await Promise.all([
fetch("/api/daily/status"), fetch("/api/daily/status"),
@ -89,7 +69,7 @@ export async function loadEvents() {
]); ]);
if (statusRes.ok) completedToday = (await statusRes.json()).completed || []; if (statusRes.ok) completedToday = (await statusRes.json()).completed || [];
if (hudRes.ok) { if (hudRes.ok) {
const hud = await hudRes.json(); const hud = await hudRes.json();
playerWood = hud.wood || 0; playerWood = hud.wood || 0;
playerGold = hud.gold || 0; playerGold = hud.gold || 0;
} }
@ -99,30 +79,24 @@ export async function loadEvents() {
body.innerHTML = ` body.innerHTML = `
<div class="events-grid" id="events-grid"> <div class="events-grid" id="events-grid">
${events ${events.map(ev => {
.map((ev) => { const done = completedToday.includes(ev.id);
const done = completedToday.includes(ev.id); const locked = !done && (
const locked = (ev.woodCost && playerWood < ev.woodCost) ||
!done && (ev.goldCost && playerGold < ev.goldCost)
((ev.woodCost && playerWood < ev.woodCost) || );
(ev.goldCost && playerGold < ev.goldCost)); const costLabel = ev.woodCost ? `🪵 ${ev.woodCost} Holz` : ev.goldCost ? `🪙 ${ev.goldCost} Gold` : '';
const costLabel = ev.woodCost const cls = done ? ' event-done' : locked ? ' event-locked' : '';
? `🪵 ${ev.woodCost} Holz` return `
: ev.goldCost <div class="event-card${cls}" data-event-id="${ev.id}" data-type="${ev.type || ''}" data-done="${done}" data-locked="${locked}">
? `🪙 ${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"> <div class="event-card-img-wrap">
<img src="${ev.img}" alt="${ev.label}" draggable="false"> <img src="${ev.img}" alt="${ev.label}" draggable="false">
${done ? `<div class="event-done-overlay"></div>` : ""} ${done ? `<div class="event-done-overlay"></div>` : ''}
${locked ? `<div class="event-locked-overlay"><span>${costLabel}<br>benötigt</span></div>` : ""} ${locked ? `<div class="event-locked-overlay"><span>${costLabel}<br>benötigt</span></div>` : ''}
</div> </div>
<span class="event-card-label">${ev.label}</span> <span class="event-card-label">${ev.label}</span>
</div>`; </div>`;
}) }).join("")}
.join("")}
</div> </div>
<!-- Booster UI --> <!-- Booster UI -->
@ -134,15 +108,12 @@ export async function loadEvents() {
<span class="booster-stapel-hint" id="booster-hint">Klicken zum Öffnen</span> <span class="booster-stapel-hint" id="booster-hint">Klicken zum Öffnen</span>
</div> </div>
<div class="booster-slots" id="booster-slots"> <div class="booster-slots" id="booster-slots">
${Array.from( ${Array.from({length: 5}, (_, i) => `
{ length: 5 },
(_, i) => `
<div class="booster-slot" id="booster-slot-${i}"> <div class="booster-slot" id="booster-slot-${i}">
<div class="booster-slot-inner"> <div class="booster-slot-inner">
<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false"> <img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">
</div> </div>
</div>`, </div>`).join("")}
).join("")}
</div> </div>
</div> </div>
</div> </div>
@ -174,7 +145,7 @@ export async function loadEvents() {
<div class="booster-stage"> <div class="booster-stage">
<div class="booster-left"> <div class="booster-left">
<div class="wood-btn-wrap"> <div class="wood-btn-wrap">
<img id="gold-btn" src="/images/items/goldmuenze.png" alt="Gold Spenden" draggable="false" class="booster-stapel-img"> <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 class="gold-stamp" id="gold-stamp">Bitte Spenden</div>
</div> </div>
<span class="booster-stapel-hint" id="gold-hint">100 Gold spenden</span> <span class="booster-stapel-hint" id="gold-hint">100 Gold spenden</span>
@ -200,46 +171,56 @@ export async function loadEvents() {
</div> </div>
`; `;
const overlay = body.querySelector("#event-detail-overlay"); const overlay = body.querySelector("#event-detail-overlay");
const edpImg = body.querySelector("#edp-img"); const edpImg = body.querySelector("#edp-img");
const edpTitle = body.querySelector("#edp-title"); const edpTitle = body.querySelector("#edp-title");
const edpBody = body.querySelector("#edp-body"); const edpBody = body.querySelector("#edp-body");
const boosterUi = body.querySelector("#booster-ui"); const boosterUi = body.querySelector("#booster-ui");
const woodUi = body.querySelector("#wood-ui"); const woodUi = body.querySelector("#wood-ui");
const goldUi = body.querySelector("#gold-ui"); const goldUi = body.querySelector("#gold-ui");
const eventsGrid = body.querySelector("#events-grid"); const eventsGrid = body.querySelector("#events-grid");
/* ── Event-Karten ── */ /* ── Event-Karten ── */
body.querySelectorAll(".event-card").forEach((card) => { body.querySelectorAll(".event-card").forEach(card => {
card.addEventListener("click", () => { card.addEventListener("click", () => {
if (card.dataset.done === "true") return; if (card.dataset.done === "true") return;
if (card.dataset.locked === "true") return; if (card.dataset.locked === "true") return;
if (card.dataset.type === "booster") { if (card.dataset.type === "booster") {
eventsGrid.style.display = "none"; eventsGrid.style.display = "none";
boosterUi.style.display = "flex"; boosterUi.style.display = "flex";
resetBooster(); resetBooster();
return; return;
} }
if (card.dataset.type === "arena") {
// Arena-Popup öffnen nur 1v1, Daily-Callback mitgeben
import("/js/quickmenu/arena.js").then(mod => {
mod.loadArena({
dailyOnly: true,
onMatchComplete: () => markDailyComplete(2),
});
});
return;
}
if (card.dataset.type === "gold") { if (card.dataset.type === "gold") {
eventsGrid.style.display = "none"; eventsGrid.style.display = "none";
goldUi.style.display = "flex"; goldUi.style.display = "flex";
resetGold(); resetGold();
return; return;
} }
if (card.dataset.type === "wood") { if (card.dataset.type === "wood") {
eventsGrid.style.display = "none"; eventsGrid.style.display = "none";
woodUi.style.display = "flex"; woodUi.style.display = "flex";
resetWood(); resetWood();
return; return;
} }
const id = Number(card.dataset.eventId); const id = Number(card.dataset.eventId);
const ev = events.find((e) => e.id === id); const ev = events.find(e => e.id === id);
if (!ev) return; if (!ev) return;
edpImg.src = ev.img; edpImg.src = ev.img;
edpImg.alt = ev.label; edpImg.alt = ev.label;
edpTitle.textContent = ev.label; edpTitle.textContent = ev.label;
edpBody.textContent = "Inhalt folgt..."; edpBody.textContent = "Inhalt folgt...";
overlay.classList.add("active"); overlay.classList.add("active");
}); });
}); });
@ -253,24 +234,16 @@ export async function loadEvents() {
body: JSON.stringify({ eventId }), body: JSON.stringify({ eventId }),
}); });
// Karte visuell als erledigt markieren // Karte visuell als erledigt markieren
const card = body.querySelector( const card = body.querySelector(`.event-card[data-event-id="${eventId}"]`);
`.event-card[data-event-id="${eventId}"]`,
);
if (card) { if (card) {
card.dataset.done = "true"; card.dataset.done = "true";
card.classList.add("event-done"); card.classList.add("event-done");
const wrap = card.querySelector(".event-card-img-wrap"); const wrap = card.querySelector(".event-card-img-wrap");
if (wrap && !wrap.querySelector(".event-done-overlay")) { if (wrap && !wrap.querySelector(".event-done-overlay")) {
wrap.insertAdjacentHTML( wrap.insertAdjacentHTML("beforeend", `<div class="event-done-overlay">✓</div>`);
"beforeend",
`<div class="event-done-overlay">✓</div>`,
);
} }
if (!card.querySelector(".event-done-label")) { if (!card.querySelector(".event-done-label")) {
card.insertAdjacentHTML( card.insertAdjacentHTML("beforeend", `<span class="event-done-label">Bereits erledigt</span>`);
"beforeend",
`<span class="event-done-label">Bereits erledigt</span>`,
);
} }
} }
} catch (e) { } catch (e) {
@ -278,12 +251,8 @@ export async function loadEvents() {
} }
} }
body body.querySelector("#edp-close-btn").addEventListener("click", () => overlay.classList.remove("active"));
.querySelector("#edp-close-btn") overlay.addEventListener("click", e => { if (e.target === overlay) overlay.classList.remove("active"); });
.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", () => { body.querySelector("#booster-back-btn").addEventListener("click", () => {
if (isSpinning) return; if (isSpinning) return;
@ -291,7 +260,7 @@ export async function loadEvents() {
const boosterDone = boosterCard?.dataset.done === "true"; const boosterDone = boosterCard?.dataset.done === "true";
if (!allRevealed && !boosterDone) return; if (!allRevealed && !boosterDone) return;
eventsGrid.style.display = ""; eventsGrid.style.display = "";
boosterUi.style.display = "none"; boosterUi.style.display = "none";
clearAllIntervals(); clearAllIntervals();
isSpinning = false; isSpinning = false;
}); });
@ -302,7 +271,7 @@ export async function loadEvents() {
const woodDone = woodCard?.dataset.done === "true"; const woodDone = woodCard?.dataset.done === "true";
if (!woodRevealed && !woodDone) return; if (!woodRevealed && !woodDone) return;
eventsGrid.style.display = ""; eventsGrid.style.display = "";
woodUi.style.display = "none"; woodUi.style.display = "none";
clearWoodIntervals(); clearWoodIntervals();
isWoodSpinning = false; isWoodSpinning = false;
}); });
@ -313,23 +282,23 @@ export async function loadEvents() {
const goldDone = goldCard?.dataset.done === "true"; const goldDone = goldCard?.dataset.done === "true";
if (!goldRevealed && !goldDone) return; if (!goldRevealed && !goldDone) return;
eventsGrid.style.display = ""; eventsGrid.style.display = "";
goldUi.style.display = "none"; goldUi.style.display = "none";
clearGoldIntervals(); clearGoldIntervals();
isGoldSpinning = false; isGoldSpinning = false;
}); });
// ESC wird zentral in quickmenu.js behandelt (verhindert Listener-Stapelung) // ESC wird zentral in quickmenu.js behandelt (verhindert Listener-Stapelung)
/* ── Booster Zustand ── */ /* ── Booster Zustand ── */
let allCards = []; let allCards = [];
let isSpinning = false; let isSpinning = false;
let allRevealed = false; // true sobald alle 5 Karten aufgedeckt + gespeichert let allRevealed = false; // true sobald alle 5 Karten aufgedeckt + gespeichert
let spinIntervals = {}; // { index: intervalId } let spinIntervals = {}; // { index: intervalId }
let slotLocked = {}; // { index: true } → verhindert Überschreiben nach reveal let slotLocked = {}; // { index: true } → verhindert Überschreiben nach reveal
function clearAllIntervals() { function clearAllIntervals() {
Object.values(spinIntervals).forEach((id) => clearInterval(id)); Object.values(spinIntervals).forEach(id => clearInterval(id));
spinIntervals = {}; spinIntervals = {};
slotLocked = {}; slotLocked = {};
} }
async function preloadCards() { async function preloadCards() {
@ -345,23 +314,19 @@ export async function loadEvents() {
function resetBooster() { function resetBooster() {
clearAllIntervals(); clearAllIntervals();
isSpinning = false; isSpinning = false;
allRevealed = false; allRevealed = false;
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
const inner = body.querySelector( const inner = body.querySelector(`#booster-slot-${i} .booster-slot-inner`);
`#booster-slot-${i} .booster-slot-inner`,
);
inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`; inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
body body.querySelector(`#booster-slot-${i}`).classList.remove("revealed", "spinning");
.querySelector(`#booster-slot-${i}`)
.classList.remove("revealed", "spinning");
} }
const stapel = body.querySelector("#booster-stapel"); const stapel = body.querySelector("#booster-stapel");
stapel.classList.remove("used"); stapel.classList.remove("used");
stapel.style.opacity = "1"; stapel.style.opacity = "1";
stapel.style.cursor = "pointer"; stapel.style.cursor = "pointer";
body.querySelector("#booster-hint").textContent = "Klicken zum Öffnen"; body.querySelector("#booster-hint").textContent = "Klicken zum Öffnen";
preloadCards(); preloadCards();
} }
@ -369,7 +334,7 @@ export async function loadEvents() {
/* ── Slot drehen 350ms, damit man die Karten erkennt ── */ /* ── Slot drehen 350ms, damit man die Karten erkennt ── */
function startSpinSlot(index) { function startSpinSlot(index) {
slotLocked[index] = false; slotLocked[index] = false;
const slot = body.querySelector(`#booster-slot-${index}`); const slot = body.querySelector(`#booster-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.add("spinning"); slot.classList.add("spinning");
@ -384,13 +349,13 @@ export async function loadEvents() {
/* ── Slot enthüllen Sperre setzen BEVOR clearInterval ── */ /* ── Slot enthüllen Sperre setzen BEVOR clearInterval ── */
function revealSlot(index, card) { function revealSlot(index, card) {
slotLocked[index] = true; // zuerst sperren slotLocked[index] = true; // zuerst sperren
clearInterval(spinIntervals[index]); // dann stoppen clearInterval(spinIntervals[index]); // dann stoppen
delete spinIntervals[index]; delete spinIntervals[index];
console.log(`[Booster] Slot ${index} enthüllt:`, card); console.log(`[Booster] Slot ${index} enthüllt:`, card);
const slot = body.querySelector(`#booster-slot-${index}`); const slot = body.querySelector(`#booster-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.remove("spinning"); slot.classList.remove("spinning");
slot.classList.add("revealed"); slot.classList.add("revealed");
@ -415,17 +380,17 @@ export async function loadEvents() {
const stapel = body.querySelector("#booster-stapel"); const stapel = body.querySelector("#booster-stapel");
stapel.classList.add("used"); stapel.classList.add("used");
stapel.style.opacity = "0.35"; stapel.style.opacity = "0.35";
stapel.style.cursor = "default"; stapel.style.cursor = "default";
body.querySelector("#booster-hint").textContent = "Wird gezogen..."; body.querySelector("#booster-hint").textContent = "Wird gezogen...";
// Zurück-Button während der Animation sperren // Zurück-Button während der Animation sperren
const backBtn = body.querySelector("#booster-back-btn"); const backBtn = body.querySelector("#booster-back-btn");
backBtn.style.opacity = "0.35"; backBtn.style.opacity = "0.35";
backBtn.style.cursor = "not-allowed"; backBtn.style.cursor = "not-allowed";
let drawnCards = []; let drawnCards = [];
try { try {
const res = await fetch("/api/booster/open", { method: "POST" }); const res = await fetch("/api/booster/open", { method: "POST" });
const text = await res.text(); const text = await res.text();
console.log("[Booster] API Antwort raw:", text); console.log("[Booster] API Antwort raw:", text);
const data = JSON.parse(text); const data = JSON.parse(text);
@ -444,37 +409,32 @@ export async function loadEvents() {
setTimeout(() => revealSlot(i, drawnCards[i]), (i + 1) * 5000); setTimeout(() => revealSlot(i, drawnCards[i]), (i + 1) * 5000);
} }
setTimeout( setTimeout(async () => {
async () => { body.querySelector("#booster-hint").textContent = "Karten werden gespeichert...";
body.querySelector("#booster-hint").textContent = isSpinning = false;
"Karten werden gespeichert...";
isSpinning = false;
// Karten in user_cards speichern // Karten in user_cards speichern
try { try {
const cardIds = drawnCards.map((c) => c.id); const cardIds = drawnCards.map(c => c.id);
const saveRes = await fetch("/api/booster/save", { const saveRes = await fetch("/api/booster/save", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cardIds }), body: JSON.stringify({ cardIds }),
}); });
if (!saveRes.ok) throw new Error(saveRes.status); if (!saveRes.ok) throw new Error(saveRes.status);
} catch (e) { } catch (e) {
console.error("Karten speichern fehlgeschlagen", e); console.error("Karten speichern fehlgeschlagen", e);
} }
// Booster-Daily als erledigt markieren (event_id = 1) // Booster-Daily als erledigt markieren (event_id = 1)
await markDailyComplete(1); await markDailyComplete(1);
allRevealed = true; allRevealed = true;
body.querySelector("#booster-hint").textContent = body.querySelector("#booster-hint").textContent = "Alle Karten erhalten! ✓";
"Alle Karten erhalten! ✓"; const backBtn = body.querySelector("#booster-back-btn");
const backBtn = body.querySelector("#booster-back-btn"); backBtn.style.opacity = "1";
backBtn.style.opacity = "1"; backBtn.style.cursor = "pointer";
backBtn.style.cursor = "pointer"; }, 5 * 5000 + 500);
},
5 * 5000 + 500,
);
}); });
preloadCards(); preloadCards();
@ -482,19 +442,19 @@ export async function loadEvents() {
/* ================================ /* ================================
Holz-Spenden Zustand Holz-Spenden Zustand
================================ */ ================================ */
let isWoodSpinning = false; let isWoodSpinning = false;
let woodRevealed = false; let woodRevealed = false;
let woodIntervals = {}; let woodIntervals = {};
let woodLocked = {}; let woodLocked = {};
let rarity3Cards = []; let rarity3Cards = [];
async function preloadRarity3() { async function preloadRarity3() {
if (rarity3Cards.length) return; if (rarity3Cards.length) return;
try { try {
const res = await fetch("/api/booster/cards"); const res = await fetch("/api/booster/cards");
if (!res.ok) throw new Error(res.status); if (!res.ok) throw new Error(res.status);
const all = await res.json(); const all = await res.json();
rarity3Cards = all.filter((c) => parseInt(c.rarity) === 3); rarity3Cards = all.filter(c => parseInt(c.rarity) === 3);
// Fallback: wenn keine rarity-3 vorhanden alle nehmen // Fallback: wenn keine rarity-3 vorhanden alle nehmen
if (!rarity3Cards.length) rarity3Cards = all; if (!rarity3Cards.length) rarity3Cards = all;
} catch (e) { } catch (e) {
@ -503,25 +463,25 @@ export async function loadEvents() {
} }
function clearWoodIntervals() { function clearWoodIntervals() {
Object.values(woodIntervals).forEach((id) => clearInterval(id)); Object.values(woodIntervals).forEach(id => clearInterval(id));
woodIntervals = {}; woodIntervals = {};
woodLocked = {}; woodLocked = {};
} }
function resetWood() { function resetWood() {
clearWoodIntervals(); clearWoodIntervals();
isWoodSpinning = false; isWoodSpinning = false;
woodRevealed = false; woodRevealed = false;
const inner = body.querySelector(`#wood-slot-0 .booster-slot-inner`); 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">`; inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
body.querySelector(`#wood-slot-0`).classList.remove("revealed", "spinning"); body.querySelector(`#wood-slot-0`).classList.remove("revealed", "spinning");
const btn = body.querySelector("#wood-btn"); const btn = body.querySelector("#wood-btn");
const stamp = body.querySelector("#wood-stamp"); const stamp = body.querySelector("#wood-stamp");
btn.classList.remove("used"); btn.classList.remove("used");
btn.style.opacity = "1"; btn.style.opacity = "1";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
if (stamp) stamp.style.display = ""; if (stamp) stamp.style.display = "";
body.querySelector("#wood-hint").textContent = "100 Holz spenden"; body.querySelector("#wood-hint").textContent = "100 Holz spenden";
preloadRarity3(); preloadRarity3();
@ -529,7 +489,7 @@ export async function loadEvents() {
function startWoodSlot(index) { function startWoodSlot(index) {
woodLocked[index] = false; woodLocked[index] = false;
const slot = body.querySelector(`#wood-slot-${index}`); const slot = body.querySelector(`#wood-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.add("spinning"); slot.classList.add("spinning");
@ -547,14 +507,13 @@ export async function loadEvents() {
clearInterval(woodIntervals[index]); clearInterval(woodIntervals[index]);
delete woodIntervals[index]; delete woodIntervals[index];
const slot = body.querySelector(`#wood-slot-${index}`); const slot = body.querySelector(`#wood-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.remove("spinning"); slot.classList.remove("spinning");
slot.classList.add("revealed"); slot.classList.add("revealed");
inner.innerHTML = cardHTML(card, true); inner.innerHTML = cardHTML(card, true);
setTimeout(() => { setTimeout(() => {
if (slot.classList.contains("revealed")) if (slot.classList.contains("revealed")) inner.innerHTML = cardHTML(card, true);
inner.innerHTML = cardHTML(card, true);
}, 100); }, 100);
} }
@ -565,31 +524,31 @@ export async function loadEvents() {
isWoodSpinning = true; isWoodSpinning = true;
const btn = body.querySelector("#wood-btn"); const btn = body.querySelector("#wood-btn");
const stamp = body.querySelector("#wood-stamp"); const stamp = body.querySelector("#wood-stamp");
btn.classList.add("used"); btn.classList.add("used");
btn.style.opacity = "0.35"; btn.style.opacity = "0.35";
btn.style.cursor = "default"; btn.style.cursor = "default";
if (stamp) stamp.style.display = "none"; if (stamp) stamp.style.display = "none";
body.querySelector("#wood-hint").textContent = "Wird verarbeitet..."; body.querySelector("#wood-hint").textContent = "Wird verarbeitet...";
const backBtn = body.querySelector("#wood-back-btn"); const backBtn = body.querySelector("#wood-back-btn");
backBtn.style.opacity = "0.35"; backBtn.style.opacity = "0.35";
backBtn.style.cursor = "not-allowed"; backBtn.style.cursor = "not-allowed";
// API aufrufen 100 Holz abziehen + Karte ziehen // API aufrufen 100 Holz abziehen + Karte ziehen
let drawnCard = null; let drawnCard = null;
try { try {
const res = await fetch("/api/booster/wood-donate", { method: "POST" }); const res = await fetch("/api/booster/wood-donate", { method: "POST" });
const data = await res.json(); const data = await res.json();
if (!res.ok) { if (!res.ok) {
body.querySelector("#wood-hint").textContent = data.error || "Fehler"; body.querySelector("#wood-hint").textContent = data.error || "Fehler";
isWoodSpinning = false; isWoodSpinning = false;
btn.classList.remove("used"); btn.classList.remove("used");
btn.style.opacity = "1"; btn.style.opacity = "1";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
backBtn.style.opacity = "1"; backBtn.style.opacity = "1";
backBtn.style.cursor = "pointer"; backBtn.style.cursor = "pointer";
return; return;
} }
drawnCard = data.card; drawnCard = data.card;
@ -608,12 +567,12 @@ export async function loadEvents() {
body.querySelector("#wood-hint").textContent = "Karte erhalten! ✓"; body.querySelector("#wood-hint").textContent = "Karte erhalten! ✓";
isWoodSpinning = false; isWoodSpinning = false;
woodRevealed = true; woodRevealed = true;
markDailyComplete(4); markDailyComplete(4);
backBtn.style.opacity = "1"; backBtn.style.opacity = "1";
backBtn.style.cursor = "pointer"; backBtn.style.cursor = "pointer";
}, 5000); }, 5000);
}); });
@ -623,30 +582,30 @@ export async function loadEvents() {
Gold-Spenden Zustand Gold-Spenden Zustand
================================ */ ================================ */
let isGoldSpinning = false; let isGoldSpinning = false;
let goldRevealed = false; let goldRevealed = false;
let goldIntervals = {}; let goldIntervals = {};
let goldLocked = {}; let goldLocked = {};
function clearGoldIntervals() { function clearGoldIntervals() {
Object.values(goldIntervals).forEach((id) => clearInterval(id)); Object.values(goldIntervals).forEach(id => clearInterval(id));
goldIntervals = {}; goldIntervals = {};
goldLocked = {}; goldLocked = {};
} }
function resetGold() { function resetGold() {
clearGoldIntervals(); clearGoldIntervals();
isGoldSpinning = false; isGoldSpinning = false;
goldRevealed = false; goldRevealed = false;
const inner = body.querySelector(`#gold-slot-0 .booster-slot-inner`); 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">`; inner.innerHTML = `<img class="booster-slot-img" src="/images/items/rueckseite.png" alt="?" draggable="false">`;
body.querySelector(`#gold-slot-0`).classList.remove("revealed", "spinning"); body.querySelector(`#gold-slot-0`).classList.remove("revealed", "spinning");
const btn = body.querySelector("#gold-btn"); const btn = body.querySelector("#gold-btn");
const stamp = body.querySelector("#gold-stamp"); const stamp = body.querySelector("#gold-stamp");
btn.classList.remove("used"); btn.classList.remove("used");
btn.style.opacity = "1"; btn.style.opacity = "1";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
if (stamp) stamp.style.display = ""; if (stamp) stamp.style.display = "";
body.querySelector("#gold-hint").textContent = "100 Gold spenden"; body.querySelector("#gold-hint").textContent = "100 Gold spenden";
preloadRarity3(); preloadRarity3();
@ -654,7 +613,7 @@ export async function loadEvents() {
function startGoldSlot(index) { function startGoldSlot(index) {
goldLocked[index] = false; goldLocked[index] = false;
const slot = body.querySelector(`#gold-slot-${index}`); const slot = body.querySelector(`#gold-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.add("spinning"); slot.classList.add("spinning");
@ -672,14 +631,13 @@ export async function loadEvents() {
clearInterval(goldIntervals[index]); clearInterval(goldIntervals[index]);
delete goldIntervals[index]; delete goldIntervals[index];
const slot = body.querySelector(`#gold-slot-${index}`); const slot = body.querySelector(`#gold-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner"); const inner = slot.querySelector(".booster-slot-inner");
slot.classList.remove("spinning"); slot.classList.remove("spinning");
slot.classList.add("revealed"); slot.classList.add("revealed");
inner.innerHTML = cardHTML(card, true); inner.innerHTML = cardHTML(card, true);
setTimeout(() => { setTimeout(() => {
if (slot.classList.contains("revealed")) if (slot.classList.contains("revealed")) inner.innerHTML = cardHTML(card, true);
inner.innerHTML = cardHTML(card, true);
}, 100); }, 100);
} }
@ -690,31 +648,31 @@ export async function loadEvents() {
isGoldSpinning = true; isGoldSpinning = true;
const btn = body.querySelector("#gold-btn"); const btn = body.querySelector("#gold-btn");
const stamp = body.querySelector("#gold-stamp"); const stamp = body.querySelector("#gold-stamp");
btn.classList.add("used"); btn.classList.add("used");
btn.style.opacity = "0.35"; btn.style.opacity = "0.35";
btn.style.cursor = "default"; btn.style.cursor = "default";
if (stamp) stamp.style.display = "none"; if (stamp) stamp.style.display = "none";
body.querySelector("#gold-hint").textContent = "Wird verarbeitet..."; body.querySelector("#gold-hint").textContent = "Wird verarbeitet...";
const backBtn = body.querySelector("#gold-back-btn"); const backBtn = body.querySelector("#gold-back-btn");
backBtn.style.opacity = "0.35"; backBtn.style.opacity = "0.35";
backBtn.style.cursor = "not-allowed"; backBtn.style.cursor = "not-allowed";
let drawnCard = null; let drawnCard = null;
try { try {
const res = await fetch("/api/booster/gold-donate", { method: "POST" }); const res = await fetch("/api/booster/gold-donate", { method: "POST" });
const data = await res.json(); const data = await res.json();
if (!res.ok) { if (!res.ok) {
body.querySelector("#gold-hint").textContent = data.error || "Fehler"; body.querySelector("#gold-hint").textContent = data.error || "Fehler";
isGoldSpinning = false; isGoldSpinning = false;
btn.classList.remove("used"); btn.classList.remove("used");
btn.style.opacity = "1"; btn.style.opacity = "1";
btn.style.cursor = "pointer"; btn.style.cursor = "pointer";
if (stamp) stamp.style.display = ""; if (stamp) stamp.style.display = "";
backBtn.style.opacity = "1"; backBtn.style.opacity = "1";
backBtn.style.cursor = "pointer"; backBtn.style.cursor = "pointer";
return; return;
} }
drawnCard = data.card; drawnCard = data.card;
@ -730,10 +688,10 @@ export async function loadEvents() {
revealGoldSlot(0, drawnCard); revealGoldSlot(0, drawnCard);
body.querySelector("#gold-hint").textContent = "Karte erhalten! ✓"; body.querySelector("#gold-hint").textContent = "Karte erhalten! ✓";
isGoldSpinning = false; isGoldSpinning = false;
goldRevealed = true; goldRevealed = true;
markDailyComplete(5); markDailyComplete(5);
backBtn.style.opacity = "1"; backBtn.style.opacity = "1";
backBtn.style.cursor = "pointer"; backBtn.style.cursor = "pointer";
}, 5000); }, 5000);
}); });
} }