diff --git a/public/css/events.css b/public/css/events.css index c2c5aee..9fd464a 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 { @@ -413,19 +413,23 @@ } /* Wood-UI: Bild + Slot auf gleiche Größe wie Booster-Slots zwingen */ -#wood-ui .booster-stage { +#wood-ui .booster-stage, +#gold-ui .booster-stage { justify-content: center; align-items: center; gap: 20px; } #wood-ui .booster-left, -#wood-ui .booster-slot { +#wood-ui .booster-slot, +#gold-ui .booster-left, +#gold-ui .booster-slot { flex: 0 0 calc((100% - 50px) / 6); max-width: calc((100% - 50px) / 6); } -.wood-stamp { +.wood-stamp, +.gold-stamp { position: absolute; top: 50%; left: 50%; @@ -436,7 +440,7 @@ color: #e85010; background: rgba(0, 0, 0, 0.5); font-family: "Cinzel", serif; - font-size: 14px; + font-size: 8px; font-weight: bold; letter-spacing: 1px; white-space: nowrap; @@ -445,6 +449,13 @@ text-shadow: 0 0 6px rgba(200, 64, 10, 0.6); } +.gold-stamp { + border-color: #b8960a; + color: #f0c020; + box-shadow: 0 0 6px rgba(200, 160, 10, 0.5); + text-shadow: 0 0 6px rgba(200, 160, 10, 0.6); +} + .event-done-overlay { position: absolute; inset: 0; diff --git a/public/js/quickmenu.js b/public/js/quickmenu.js index 1a30304..9ce799d 100644 --- a/public/js/quickmenu.js +++ b/public/js/quickmenu.js @@ -117,6 +117,9 @@ function isBoosterSpinning() { const woodUi = document.getElementById("wood-ui"); if (woodUi && woodUi.style.display !== "none" && woodUi.querySelector(".booster-slot.spinning")) return true; + const goldUi = document.getElementById("gold-ui"); + if (goldUi && goldUi.style.display !== "none" && goldUi.querySelector(".booster-slot.spinning")) return true; + return false; } diff --git a/public/js/quickmenu/events.js b/public/js/quickmenu/events.js index 8e6edf9..b4d9eb1 100644 --- a/public/js/quickmenu/events.js +++ b/public/js/quickmenu/events.js @@ -55,19 +55,24 @@ export async function loadEvents() { { id: 2, img: "/images/items/runenhaufen.png", label: "Textzeile 2" }, { id: 3, img: "/images/items/runenhaufen.png", label: "Textzeile 3" }, { id: 4, img: "/images/items/holz.png", label: "Holz Spenden", type: "wood", woodCost: 100 }, - { id: 5, img: "/images/items/runenhaufen.png", label: "Textzeile 5" }, + { 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) playerWood = (await hudRes.json()).wood || 0; + if (hudRes.ok) { + const hud = await hudRes.json(); + playerWood = hud.wood || 0; + playerGold = hud.gold || 0; + } } catch (e) { console.error("Laden fehlgeschlagen", e); } @@ -76,14 +81,18 @@ export async function loadEvents() {
${events.map(ev => { const done = completedToday.includes(ev.id); - const locked = !done && ev.woodCost && playerWood < ev.woodCost; + 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 `
${ev.label} ${done ? `
` : ''} - ${locked ? `
🪵 ${ev.woodCost} Holz
benötigt
` : ''} + ${locked ? `
${costLabel}
benötigt
` : ''}
${ev.label}
`; @@ -130,6 +139,27 @@ export async function loadEvents() {
+ + +
@@ -147,6 +177,7 @@ export async function loadEvents() { 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 eventsGrid = body.querySelector("#events-grid"); /* ── Event-Karten ── */ @@ -161,6 +192,12 @@ export async function loadEvents() { resetBooster(); 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"; @@ -229,7 +266,16 @@ export async function loadEvents() { isWoodSpinning = false; }); - // KEIN document.addEventListener("keydown") hier – + 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; + }); // ESC wird zentral in quickmenu.js behandelt (verhindert Listener-Stapelung) /* ── Booster Zustand ── */ @@ -521,4 +567,121 @@ export async function loadEvents() { }); 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); + }); } diff --git a/routes/booster.js b/routes/booster.js index bad06a0..469c415 100644 --- a/routes/booster.js +++ b/routes/booster.js @@ -188,4 +188,44 @@ router.post("/booster/wood-donate", async (req, res) => { } }); +/* ================================ + POST /api/booster/gold-donate + 100 Gold bezahlen → 1 zufällige Rarity-3 Karte +================================ */ +router.post("/booster/gold-donate", async (req, res) => { + if (!req.session?.user) return res.status(401).json({ error: "Nicht eingeloggt" }); + + const userId = req.session.user.id; + try { + const [[currency]] = await db.query( + "SELECT gold FROM account_currency WHERE account_id = ?", [userId] + ); + if (!currency || currency.gold < 100) { + return res.status(400).json({ error: "Nicht genug Gold (100 benötigt)" }); + } + + 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" }); + + await db.query( + "UPDATE account_currency SET gold = gold - 100 WHERE account_id = ?", [userId] + ); + + const card = pool[Math.floor(Math.random() * pool.length)]; + + 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;