diff --git a/public/css/events.css b/public/css/events.css
index f138ec5..3e50f2d 100644
--- a/public/css/events.css
+++ b/public/css/events.css
@@ -31,7 +31,7 @@
justify-content: center;
padding: 6px;
box-sizing: border-box;
- position: relative; /* ← fix: damit event-done-overlay korrekt positioniert wird */
+ position: relative; /* ← fix: damit event-done-overlay korrekt positioniert wird */
}
.event-card-img-wrap img {
@@ -375,6 +375,37 @@
cursor: not-allowed;
}
+.event-locked {
+ cursor: not-allowed;
+ opacity: 0.6;
+}
+
+.event-locked-overlay {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0.55);
+ border-radius: inherit;
+ pointer-events: none;
+}
+
+.event-locked-overlay span {
+ display: block;
+ padding: 4px 7px;
+ border: 2px solid #a05010;
+ border-radius: 5px;
+ color: #e08030;
+ font-family: "Cinzel", serif;
+ font-size: 9px;
+ font-weight: bold;
+ text-align: center;
+ line-height: 1.4;
+ background: rgba(0, 0, 0, 0.5);
+ white-space: nowrap;
+}
+
.event-done-overlay {
position: absolute;
inset: 0;
@@ -395,7 +426,7 @@
border-radius: 5px;
color: #3dbb3d;
font-family: "Cinzel", serif;
- font-size: 14px;
+ font-size: 11px;
font-weight: bold;
letter-spacing: 1px;
text-transform: uppercase;
diff --git a/public/css/mine.css b/public/css/mine.css
index 785314d..2af1f60 100644
--- a/public/css/mine.css
+++ b/public/css/mine.css
@@ -119,7 +119,7 @@
flex: 1;
font-size: 13px;
font-weight: bold;
- color: #1a1008;
+ color: #ffffff;
text-shadow: none;
}
diff --git a/public/js/quickmenu.js b/public/js/quickmenu.js
index 6702498..1a30304 100644
--- a/public/js/quickmenu.js
+++ b/public/js/quickmenu.js
@@ -107,14 +107,17 @@ document.querySelectorAll(".qm-popup").forEach((popup) => {
================================ */
function isBoosterSpinning() {
- // Nur sperren wenn das Events-Popup gerade offen UND der Slot dreht
const eventsPopup = document.getElementById("qm-popup-events");
if (!eventsPopup || !eventsPopup.classList.contains("active")) return false;
- const ui = document.getElementById("booster-ui");
- if (!ui || ui.style.display === "none") return false;
+ // Booster-UI oder Wood-UI aktiv und Slot dreht
+ const boosterUi = document.getElementById("booster-ui");
+ if (boosterUi && boosterUi.style.display !== "none" && boosterUi.querySelector(".booster-slot.spinning")) return true;
- return !!ui.querySelector(".booster-slot.spinning");
+ const woodUi = document.getElementById("wood-ui");
+ if (woodUi && woodUi.style.display !== "none" && woodUi.querySelector(".booster-slot.spinning")) return true;
+
+ return false;
}
document.querySelectorAll(".qm-popup-close").forEach((btn) => {
diff --git a/public/js/quickmenu/events.js b/public/js/quickmenu/events.js
index c7223ef..dc96ffb 100644
--- a/public/js/quickmenu/events.js
+++ b/public/js/quickmenu/events.js
@@ -20,22 +20,19 @@ function rarityImgs(rarity, size = 13) {
}
function cardHTML(card, isFront = true) {
- if (!isFront)
- return `
`;
+ 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";
+ 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 ? `
`;
- body
- .querySelector(`#booster-slot-${i}`)
- .classList.remove("revealed", "spinning");
+ 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";
+ stapel.style.cursor = "pointer";
body.querySelector("#booster-hint").textContent = "Klicken zum Öffnen";
preloadCards();
}
@@ -266,7 +276,7 @@ export async function loadEvents() {
/* ── Slot drehen – 350ms, damit man die Karten erkennt ── */
function startSpinSlot(index) {
slotLocked[index] = false;
- const slot = body.querySelector(`#booster-slot-${index}`);
+ const slot = body.querySelector(`#booster-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner");
slot.classList.add("spinning");
@@ -281,13 +291,13 @@ export async function loadEvents() {
/* ── Slot enthüllen – Sperre setzen BEVOR clearInterval ── */
function revealSlot(index, card) {
- slotLocked[index] = true; // zuerst sperren
+ 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 slot = body.querySelector(`#booster-slot-${index}`);
const inner = slot.querySelector(".booster-slot-inner");
slot.classList.remove("spinning");
slot.classList.add("revealed");
@@ -312,17 +322,17 @@ export async function loadEvents() {
const stapel = body.querySelector("#booster-stapel");
stapel.classList.add("used");
stapel.style.opacity = "0.35";
- stapel.style.cursor = "default";
+ 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";
+ backBtn.style.cursor = "not-allowed";
let drawnCards = [];
try {
- const res = await fetch("/api/booster/open", { method: "POST" });
+ 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);
@@ -341,38 +351,170 @@ export async function loadEvents() {
setTimeout(() => revealSlot(i, drawnCards[i]), (i + 1) * 5000);
}
- setTimeout(
- async () => {
- body.querySelector("#booster-hint").textContent =
- "Karten werden gespeichert...";
- isSpinning = false;
+ 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);
- }
+ // 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);
+ // 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,
- );
+ 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;
+
+ for (let i = 0; i < 5; i++) {
+ const inner = body.querySelector(`#wood-slot-${i} .booster-slot-inner`);
+ inner.innerHTML = `
`;
+ body.querySelector(`#wood-slot-${i}`).classList.remove("revealed", "spinning");
+ }
+
+ const btn = body.querySelector("#wood-btn");
+ btn.classList.remove("used");
+ btn.style.opacity = "1";
+ btn.style.cursor = "pointer";
+ 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");
+ btn.classList.add("used");
+ btn.style.opacity = "0.35";
+ btn.style.cursor = "default";
+ 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;
+ }
+
+ // Alle 5 Slots starten
+ for (let i = 0; i < 5; i++) startWoodSlot(i);
+
+ // Nach 5 Sekunden alle gleichzeitig enthüllen (dieselbe Karte)
+ setTimeout(() => {
+ for (let i = 0; i < 5; i++) revealWoodSlot(i, drawnCard);
+
+ body.querySelector("#wood-hint").textContent = "Karte erhalten! ✓";
+ isWoodSpinning = false;
+ woodRevealed = true;
+
+ markDailyComplete(4);
+
+ backBtn.style.opacity = "1";
+ backBtn.style.cursor = "pointer";
+ }, 5000);
+ });
+
+ preloadRarity3();
}
diff --git a/routes/booster.js b/routes/booster.js
index 4a23bd8..bad06a0 100644
--- a/routes/booster.js
+++ b/routes/booster.js
@@ -143,4 +143,49 @@ router.post("/booster/save", async (req, res) => {
}
});
+/* ================================
+ POST /api/booster/wood-donate
+ 100 Holz bezahlen → 1 zufällige Rarity-3 Karte
+================================ */
+router.post("/booster/wood-donate", async (req, res) => {
+ if (!req.session?.user) return res.status(401).json({ error: "Nicht eingeloggt" });
+
+ const userId = req.session.user.id;
+ try {
+ // Holz prüfen
+ const [[currency]] = await db.query(
+ "SELECT wood FROM account_currency WHERE account_id = ?", [userId]
+ );
+ if (!currency || currency.wood < 100) {
+ return res.status(400).json({ error: "Nicht genug Holz (100 benötigt)" });
+ }
+
+ // Rarity-3 Karten laden
+ const [pool] = await db.query(
+ "SELECT id, name, image, icon, max_level, rarity, attack, defends, cooldown FROM cards WHERE rarity = 3"
+ );
+ if (!pool.length) return res.status(400).json({ error: "Keine Rarity-3 Karten verfügbar" });
+
+ // 100 Holz abziehen
+ await db.query(
+ "UPDATE account_currency SET wood = wood - 100 WHERE account_id = ?", [userId]
+ );
+
+ // 1 zufällige Rarity-3 Karte ziehen
+ const card = pool[Math.floor(Math.random() * pool.length)];
+
+ // Direkt in user_cards speichern
+ await db.query(
+ `INSERT INTO user_cards (user_id, card_id, amount) VALUES (?, ?, 1)
+ ON DUPLICATE KEY UPDATE amount = amount + 1`,
+ [userId, card.id]
+ );
+
+ res.json({ card });
+ } catch (err) {
+ console.error(err);
+ res.status(500).json({ error: "DB Fehler" });
+ }
+});
+
module.exports = router;