From 13cc66e4c54028da9a409341db0678d2186da8d8 Mon Sep 17 00:00:00 2001 From: cay Date: Mon, 30 Mar 2026 16:39:19 +0100 Subject: [PATCH] serth --- app.js | 70 ++++++------- public/js/quickmenu/lucky-box.js | 167 ++++++++++++++----------------- 2 files changed, 104 insertions(+), 133 deletions(-) diff --git a/app.js b/app.js index 86c51d1..1bd3471 100644 --- a/app.js +++ b/app.js @@ -151,10 +151,10 @@ app.get("/api/building/:id", requireLogin, async (req, res) => { upgradeCost: nextLevel[0] ? `${nextLevel[0].wood} Holz, ${nextLevel[0].stone} Stein, ${nextLevel[0].gold} Gold` : "Max Level erreicht", - upgradeWood: nextLevel[0]?.wood ?? null, - upgradeStone: nextLevel[0]?.stone ?? null, - upgradeGold: nextLevel[0]?.gold ?? null, - upgradeRequiredPoints: nextLevel[0]?.required_points ?? null, // NEU + upgradeWood: nextLevel[0]?.wood ?? null, + upgradeStone: nextLevel[0]?.stone ?? null, + upgradeGold: nextLevel[0]?.gold ?? null, + upgradeRequiredPoints: nextLevel[0]?.required_points ?? null, // NEU }); } catch (err) { console.error(err); @@ -190,9 +190,7 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { ); if (!levelData) { - return res - .status(400) - .json({ error: "Maximales Level bereits erreicht" }); + return res.status(400).json({ error: "Maximales Level bereits erreicht" }); } // Punkte prüfen @@ -212,23 +210,11 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { return res.status(400).json({ error: "Keine Währungsdaten gefunden" }); } - if ( - currency.wood < levelData.wood || - currency.stone < levelData.stone || - currency.gold < levelData.gold - ) { + if (currency.wood < levelData.wood || currency.stone < levelData.stone || currency.gold < levelData.gold) { return res.status(400).json({ error: "Nicht genügend Ressourcen", - required: { - wood: levelData.wood, - stone: levelData.stone, - gold: levelData.gold, - }, - current: { - wood: currency.wood, - stone: currency.stone, - gold: currency.gold, - }, + required: { wood: levelData.wood, stone: levelData.stone, gold: levelData.gold }, + current: { wood: currency.wood, stone: currency.stone, gold: currency.gold }, }); } @@ -247,11 +233,7 @@ app.post("/api/building/:id/upgrade", requireLogin, async (req, res) => { res.json({ success: true, newLevel: nextLevel, - cost: { - wood: levelData.wood, - stone: levelData.stone, - gold: levelData.gold, - }, + cost: { wood: levelData.wood, stone: levelData.stone, gold: levelData.gold }, }); } catch (err) { console.error(err); @@ -315,12 +297,26 @@ app.get("/api/buildings", requireLogin, async (req, res) => { } }); +/* ======================== + Card Groups API +======================== */ + +app.get("/api/card-groups", requireLogin, async (req, res) => { + try { + const [groups] = await db.query("SELECT * FROM card_groups ORDER BY id"); + res.json(groups); + } catch (err) { + console.error(err); + res.status(500).json({ error: "DB Fehler" }); + } +}); + /* ======================== Cards API ======================== */ app.get("/api/cards", requireLogin, async (req, res) => { - const { race, page = 1, limit = 12 } = req.query; + const { group_id, page = 1, limit = 12 } = req.query; const offset = (page - 1) * limit; try { @@ -330,31 +326,21 @@ app.get("/api/cards", requireLogin, async (req, res) => { FROM cards c LEFT JOIN card_groups cg ON cg.id = c.group_id LEFT JOIN card_levels cl ON cl.card_id = c.id AND cl.level = 1 - WHERE c.race = ? + WHERE c.group_id = ? LIMIT ? OFFSET ?`, - [race, parseInt(limit), parseInt(offset)], + [group_id, parseInt(limit), parseInt(offset)] ); const [[{ total }]] = await db.query( - "SELECT COUNT(*) as total FROM cards WHERE race = ?", - [race], + "SELECT COUNT(*) as total FROM cards WHERE group_id = ?", [group_id] ); - res.json({ - cards, - total, - page: parseInt(page), - totalPages: Math.ceil(total / limit), - }); + res.json({ cards, total, page: parseInt(page), totalPages: Math.ceil(total / limit) }); } catch (err) { console.error(err); res.status(500).json({ error: "DB Fehler" }); } }); -/* ======================== - Body Parser -======================== */ - app.use(express.json()); app.use(express.urlencoded({ extended: true })); diff --git a/public/js/quickmenu/lucky-box.js b/public/js/quickmenu/lucky-box.js index 3001662..c72e5eb 100644 --- a/public/js/quickmenu/lucky-box.js +++ b/public/js/quickmenu/lucky-box.js @@ -1,40 +1,37 @@ const CARDS_PER_PAGE = 12; -const RACES = [ - { id: "ritter", label: "Ritter", icon: "⚔️" }, - { id: "elfen", label: "Elfen", icon: "🌿" }, - { id: "zwerge", label: "Zwerge", icon: "⛏️" }, - { id: "orks", label: "Orks", icon: "💀" }, - { id: "untote", label: "Untote", icon: "💀" }, - { id: "drachen", label: "Drachen", icon: "🐉" }, -]; - -let currentRace = RACES[0].id; -let currentPage = 1; +let currentGroupId = null; +let currentPage = 1; export async function loadLuckyBox() { const body = document.getElementById("qm-body-glucksbox"); if (!body) return; body.innerHTML = renderShell(); - attachTabEvents(); - await loadCards(); + + try { + const res = await fetch("/api/card-groups"); + if (!res.ok) throw new Error("API Fehler"); + const groups = await res.json(); + + renderTabs(groups); + + if (groups.length) { + currentGroupId = groups[0].id; + await loadCards(); + } + } catch (err) { + console.error("Gruppen Fehler:", err); + } } -/* ── HTML-Grundstruktur ─────────────────────── */ +/* ── Grundstruktur ──────────────────────────── */ function renderShell() { - const tabs = RACES.map( - (r, i) => ` - - `, - ).join(""); - return `
- +
@@ -50,7 +47,6 @@ function renderShell() { font-family: "Cinzel", serif; } - /* ── Tabs ── */ .kd-tabs { display: flex; flex-direction: column; @@ -58,8 +54,9 @@ function renderShell() { padding: 20px 10px; background: rgba(0,0,0,0.25); border-right: 2px solid #6b4b2a; - min-width: 130px; + min-width: 140px; flex-shrink: 0; + overflow-y: auto; } .kd-tab { @@ -68,7 +65,7 @@ function renderShell() { gap: 8px; padding: 10px 14px; background: linear-gradient(135deg, #2a1a08, #1a0f04); - border: 1px solid #6b4b2a; + border: 2px solid #6b4b2a; border-radius: 8px; color: #c8a86a; font-family: "Cinzel", serif; @@ -81,22 +78,23 @@ function renderShell() { } .kd-tab:hover { - border-color: #f0d060; color: #f0d9a6; - background: linear-gradient(135deg, #3a2810, #2a1a08); - box-shadow: 0 0 10px rgba(200,160,60,0.2); + filter: brightness(1.2); } .kd-tab-active { - background: linear-gradient(135deg, #6b4b2a, #3c2414) !important; - border-color: #f0d060 !important; - color: #fff5cc !important; - box-shadow: inset 0 0 10px rgba(0,0,0,0.4), 0 0 12px rgba(200,160,60,0.3) !important; + color: #fff !important; + box-shadow: inset 0 0 10px rgba(0,0,0,0.5), 0 0 14px rgba(200,160,60,0.3) !important; } - .kd-tab-icon { font-size: 18px; } + .kd-tab-dot { + width: 12px; + height: 12px; + border-radius: 50%; + flex-shrink: 0; + border: 1px solid rgba(255,255,255,0.3); + } - /* ── Main ── */ .kd-main { flex: 1; display: flex; @@ -105,7 +103,6 @@ function renderShell() { overflow: hidden; } - /* ── Grid ── */ .kd-grid { flex: 1; display: grid; @@ -115,7 +112,6 @@ function renderShell() { align-content: start; } - /* ── Karte ── */ .kd-card { position: relative; border: 2px solid #6b4b2a; @@ -155,7 +151,6 @@ function renderShell() { line-height: 1.3; } - /* Angriff / Verteidigung Badges */ .kd-stat-atk { position: absolute; bottom: 28px; @@ -184,7 +179,6 @@ function renderShell() { padding: 1px 5px; } - /* ── Leer-Zustand ── */ .kd-empty { grid-column: 1 / -1; text-align: center; @@ -193,7 +187,6 @@ function renderShell() { padding: 60px 0; } - /* ── Pagination ── */ .kd-pagination { display: flex; align-items: center; @@ -217,7 +210,6 @@ function renderShell() { .kd-page-btn:hover { border-color: #f0d060; - background: linear-gradient(#5a3820, #2a1404); } .kd-page-btn:disabled { @@ -238,13 +230,11 @@ function renderShell() { text-align: center; } - /* ── Loading ── */ .kd-loading { - grid-column: 1 / -1; text-align: center; color: #8b6a3c; font-size: 15px; - padding: 60px 0; + padding: 20px 0; animation: kd-pulse 1.2s infinite; } @@ -256,16 +246,34 @@ function renderShell() { `; } -/* ── Tab-Klick ─────────────────────────────── */ -function attachTabEvents() { - document.querySelectorAll(".kd-tab").forEach((btn) => { +/* ── Tabs aus DB ────────────────────────────── */ +function renderTabs(groups) { + const container = document.getElementById("kd-tabs"); + if (!container) return; + + container.innerHTML = groups.map((g, i) => { + const color = g.color || "#6b4b2a"; + return ` + + `; + }).join(""); + + container.querySelectorAll(".kd-tab").forEach((btn) => { btn.addEventListener("click", async () => { - document - .querySelectorAll(".kd-tab") - .forEach((b) => b.classList.remove("kd-tab-active")); + const color = btn.style.borderColor; + container.querySelectorAll(".kd-tab").forEach((b) => { + b.classList.remove("kd-tab-active"); + b.style.background = ""; + }); btn.classList.add("kd-tab-active"); - currentRace = btn.dataset.race; - currentPage = 1; + btn.style.background = `${color}33`; + currentGroupId = parseInt(btn.dataset.group); + currentPage = 1; await loadCards(); }); }); @@ -273,7 +281,7 @@ function attachTabEvents() { /* ── Karten laden ───────────────────────────── */ async function loadCards() { - const grid = document.getElementById("kd-grid"); + const grid = document.getElementById("kd-grid"); const pagination = document.getElementById("kd-pagination"); if (!grid) return; @@ -281,72 +289,49 @@ async function loadCards() { pagination.innerHTML = ""; try { - const res = await fetch( - `/api/cards?race=${currentRace}&page=${currentPage}&limit=${CARDS_PER_PAGE}`, - ); + const res = await fetch(`/api/cards?group_id=${currentGroupId}&page=${currentPage}&limit=${CARDS_PER_PAGE}`); if (!res.ok) throw new Error("API Fehler"); const data = await res.json(); - // data = { cards: [...], total: N, page: N, totalPages: N } - renderGrid(grid, data.cards); renderPagination(pagination, data.totalPages, data.total); } catch { - grid.innerHTML = `
Noch keine Karten für diese Rasse vorhanden.
`; + grid.innerHTML = `
Noch keine Karten für diese Gruppe vorhanden.
`; } } -/* ── Grid rendern ───────────────────────────── */ +/* ── Grid ───────────────────────────────────── */ function renderGrid(grid, cards) { if (!cards || !cards.length) { - grid.innerHTML = `
Noch keine Karten für diese Rasse vorhanden.
`; + grid.innerHTML = `
Noch keine Karten für diese Gruppe vorhanden.
`; return; } - - grid.innerHTML = cards - .map( - (c) => ` + grid.innerHTML = cards.map((c) => `
${c.name} - ${c.attack !== undefined ? `${c.attack}` : ""} - ${c.defense !== undefined ? `${c.defense}` : ""} + onerror="this.src='/images/avatar_placeholder.svg'"> + ${c.attack != null ? `${c.attack}` : ""} + ${c.defense != null ? `${c.defense}` : ""}
${c.name}
- `, - ) - .join(""); + `).join(""); } -/* ── Pagination rendern ─────────────────────── */ +/* ── Pagination ─────────────────────────────── */ function renderPagination(pagination, totalPages, total) { if (totalPages <= 1) return; - - const pages = Array.from({ length: totalPages }, (_, i) => i + 1); - pagination.innerHTML = ` - ${pages - .map( - (p) => ` + ${Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => ` - `, - ) - .join("")} + `).join("")} ${total} Karten `; - document.getElementById("kd-prev")?.addEventListener("click", async () => { - if (currentPage > 1) { - currentPage--; - await loadCards(); - } + if (currentPage > 1) { currentPage--; await loadCards(); } }); document.getElementById("kd-next")?.addEventListener("click", async () => { - if (currentPage < totalPages) { - currentPage++; - await loadCards(); - } + if (currentPage < totalPages) { currentPage++; await loadCards(); } }); pagination.querySelectorAll("[data-page]").forEach((btn) => { btn.addEventListener("click", async () => {