diff --git a/public/css/quickmenu.css b/public/css/quickmenu.css index b834743..03467df 100644 --- a/public/css/quickmenu.css +++ b/public/css/quickmenu.css @@ -227,7 +227,8 @@ padding: 16px 10px; background: rgba(0, 0, 0, 0.25); border-right: 2px solid #8b6a3c; - min-width: 160px; + min-width: 140px; + flex-shrink: 0; } .gaststaette-tab { @@ -245,32 +246,29 @@ text-transform: uppercase; letter-spacing: 0.5px; } - .gaststaette-tab:hover { border-color: #f0d060; background: linear-gradient(145deg, #5a3c18, #2a1a08); } - .gaststaette-tab.active { background: linear-gradient(145deg, #6b4b2a, #3c2414); border-color: #f0d060; color: #f0d060; - box-shadow: inset 0 0 8px rgba(0,0,0,0.5); } .gaststaette-content { flex: 1; - padding: 24px 40px; - overflow-y: auto; - font-family: "Cinzel", serif; - font-size: 16px; - color: #5c3b20; + overflow: hidden; + display: flex; + flex-direction: column; } .gaststaette-tab-content { display: none; + flex: 1; + overflow: hidden; } - .gaststaette-tab-content.active { - display: block; + display: flex; + flex-direction: column; } diff --git a/public/js/gaststaette/glucksspiel.js b/public/js/gaststaette/glucksspiel.js new file mode 100644 index 0000000..602f581 --- /dev/null +++ b/public/js/gaststaette/glucksspiel.js @@ -0,0 +1,641 @@ +/* ============================================================ + public/js/gaststaette/glucksspiel.js + Glücksspiel-Tab in der Gaststätte + – Karten nach Fraktion anzeigen (wie Kartendeck) + – Karten gleicher ID ins Kombinier-Feld legen + – "Kombinieren"-Button +============================================================ */ + +const CARDS_PER_PAGE = 18; + +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 = `Stufe ${rarity}`; + return img.repeat(count); +} + +// State +let gs_groupId = null; +let gs_page = 1; +let gs_cards = []; // aktuelle Seite +let gs_combineId = null; // card_id der Karte im Kombinier-Feld +let gs_combineCards = []; // [{card_id, name, image, rarity, attack, defends, cooldown}] im Feld +let gs_initialized = false; + +/* ══════════════════════════════════════════════ + INIT +══════════════════════════════════════════════ */ +export async function loadGlucksspiel() { + const container = document.getElementById("gs-tab1"); + if (!container) return; + + // Nur beim ersten Aufruf rendern + if (gs_initialized) return; + gs_initialized = true; + + container.innerHTML = renderShell(); + attachCombineEvents(); + + try { + const res = await fetch("/api/card-groups"); + if (!res.ok) throw new Error(); + const groups = await res.json(); + renderTabs(groups); + if (groups.length) { + gs_groupId = groups[0].id; + await loadCards(); + } + } catch { + container.innerHTML = `

Fehler beim Laden.

`; + } +} + +/* ══════════════════════════════════════════════ + SHELL +══════════════════════════════════════════════ */ +function renderShell() { + return ` +
+ + + + + +
+
Meine Karten
+
+
Lade Karten...
+
+
+
+ + +
+
Kombinieren
+
+ Klicke auf eine Karte links um sie hier zu platzieren.
+ Nur Karten gleicher Art können kombiniert werden. +
+
+
+ +
+ +
+ + + `; +} + +/* ══════════════════════════════════════════════ + FRAKTIONS-TABS +══════════════════════════════════════════════ */ +function renderTabs(groups) { + const container = document.getElementById("gs-faction-tabs"); + if (!container) return; + + container.innerHTML = groups + .map((g, i) => { + const color = g.color || "#6b4b2a"; + return ` + `; + }) + .join(""); + + container.querySelectorAll(".gs-tab").forEach((btn) => { + btn.addEventListener("click", async () => { + const color = btn.style.borderColor; + container.querySelectorAll(".gs-tab").forEach((b) => { + b.classList.remove("gs-tab-active"); + b.style.background = ""; + }); + btn.classList.add("gs-tab-active"); + btn.style.background = `${color}33`; + gs_groupId = parseInt(btn.dataset.group); + gs_page = 1; + await loadCards(); + }); + }); +} + +/* ══════════════════════════════════════════════ + KARTEN LADEN +══════════════════════════════════════════════ */ +async function loadCards() { + const grid = document.getElementById("gs-grid"); + const pagination = document.getElementById("gs-pagination"); + if (!grid) return; + + grid.innerHTML = `
Lade Karten...
`; + if (pagination) pagination.innerHTML = ""; + + try { + const res = await fetch( + `/api/user-cards?group_id=${gs_groupId}&page=${gs_page}&limit=${CARDS_PER_PAGE}` + ); + if (!res.ok) throw new Error(); + const data = await res.json(); + gs_cards = data.cards || []; + renderGrid(grid, gs_cards); + renderPagination(pagination, data.totalPages, data.total); + } catch { + grid.innerHTML = `
Keine Karten gefunden.
`; + } +} + +/* ══════════════════════════════════════════════ + KARTEN-GRID RENDERN +══════════════════════════════════════════════ */ +function renderGrid(grid, cards) { + if (!cards || !cards.length) { + grid.innerHTML = `
Keine Karten in dieser Gruppe.
`; + return; + } + + grid.innerHTML = cards.map((c) => { + // Karte deaktivieren wenn sie nicht zur aktuellen Kombination passt + const disabled = gs_combineId !== null && c.card_id !== gs_combineId; + // Anzahl bereits im Kombinier-Feld + const inField = gs_combineCards.filter(x => x.card_id === c.card_id).length; + const available = c.amount - inField; + + return ` +
+ ${c.name} + ${c.attack != null ? `${c.attack}` : ""} + ${c.defends != null ? `${c.defends}` : ""} + + ${c.rarity ? `
${rarityImgs(c.rarity, 12)}
` : ""} +
`; + }).join(""); + + grid.querySelectorAll(".gs-card:not(.gs-card-disabled)").forEach((el) => { + el.addEventListener("click", () => { + const card = cards.find((c) => c.card_id === parseInt(el.dataset.cardId)); + if (!card) return; + + const inField = gs_combineCards.filter(x => x.card_id === card.card_id).length; + if (inField >= card.amount) { + showToast("Keine weiteren Exemplare verfügbar."); + return; + } + addToField(card); + }); + }); +} + +/* ══════════════════════════════════════════════ + KARTE ZUM KOMBINIER-FELD HINZUFÜGEN +══════════════════════════════════════════════ */ +function addToField(card) { + gs_combineId = card.card_id; + gs_combineCards.push({ ...card }); + renderCombineField(); + renderGrid(document.getElementById("gs-grid"), gs_cards); +} + +/* ══════════════════════════════════════════════ + KARTE AUS FELD ENTFERNEN +══════════════════════════════════════════════ */ +function removeFromField(index) { + gs_combineCards.splice(index, 1); + if (gs_combineCards.length === 0) gs_combineId = null; + renderCombineField(); + renderGrid(document.getElementById("gs-grid"), gs_cards); +} + +/* ══════════════════════════════════════════════ + KOMBINIER-FELD RENDERN +══════════════════════════════════════════════ */ +function renderCombineField() { + const grid = document.getElementById("gs-combine-grid"); + const hint = document.getElementById("gs-combine-hint"); + const count = document.getElementById("gs-combine-count"); + const btn = document.getElementById("gs-combine-btn"); + if (!grid) return; + + if (!gs_combineCards.length) { + grid.innerHTML = ""; + hint.style.display = "block"; + count.textContent = ""; + btn.disabled = true; + return; + } + + hint.style.display = "none"; + + grid.innerHTML = gs_combineCards.map((c, i) => ` +
+ ${c.name} +
+ `).join(""); + + grid.querySelectorAll(".gs-combine-card").forEach((el) => { + el.addEventListener("click", () => { + removeFromField(parseInt(el.dataset.index)); + }); + }); + + count.textContent = `${gs_combineCards.length} Karte${gs_combineCards.length !== 1 ? "n" : ""} im Feld`; + btn.disabled = gs_combineCards.length < 2; +} + +/* ══════════════════════════════════════════════ + KOMBINIEREN-BUTTON +══════════════════════════════════════════════ */ +function attachCombineEvents() { + // Wird nach renderShell aufgerufen + setTimeout(() => { + const btn = document.getElementById("gs-combine-btn"); + if (!btn) return; + btn.addEventListener("click", async () => { + if (gs_combineCards.length < 2) return; + + btn.disabled = true; + btn.textContent = "Kombiniere..."; + + try { + const res = await fetch("/api/cards/combine", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + card_id: gs_combineId, + amount: gs_combineCards.length, + }), + }); + + const data = await res.json(); + + if (!res.ok) { + showToast(data.error || "Kombinieren fehlgeschlagen."); + btn.disabled = false; + btn.textContent = "Kombinieren"; + return; + } + + showToast("Kombinieren erfolgreich! 🎉"); + gs_combineCards = []; + gs_combineId = null; + gs_initialized = false; // Neu laden erzwingen + renderCombineField(); + await loadCards(); + } catch { + showToast("Verbindungsfehler."); + btn.disabled = false; + btn.textContent = "Kombinieren"; + } finally { + btn.textContent = "Kombinieren"; + } + }); + }, 50); +} + +/* ══════════════════════════════════════════════ + PAGINATION +══════════════════════════════════════════════ */ +function renderPagination(pagination, totalPages, total) { + if (!pagination || !totalPages || totalPages <= 1) return; + pagination.innerHTML = ` + + ${Array.from({ length: totalPages }, (_, i) => i + 1) + .map((p) => ``) + .join("")} + + ${total} Karten`; + + pagination.querySelector("#gs-prev")?.addEventListener("click", async () => { + if (gs_page > 1) { gs_page--; await loadCards(); } + }); + pagination.querySelector("#gs-next")?.addEventListener("click", async () => { + if (gs_page < totalPages) { gs_page++; await loadCards(); } + }); + pagination.querySelectorAll("[data-page]").forEach((btn) => { + btn.addEventListener("click", async () => { + gs_page = parseInt(btn.dataset.page); + await loadCards(); + }); + }); +} + +/* ══════════════════════════════════════════════ + TOAST +══════════════════════════════════════════════ */ +function showToast(msg) { + const existing = document.querySelector(".gs-toast"); + if (existing) existing.remove(); + const t = document.createElement("div"); + t.className = "gs-toast"; + t.textContent = msg; + t.style.cssText = ` + position:fixed;bottom:80px;left:50%;transform:translateX(-50%); + background:linear-gradient(#4a2808,#2a1004);border:2px solid #8b6a3c; + border-radius:8px;color:#f0d9a6;font-family:"Cinzel",serif;font-size:13px; + padding:10px 22px;z-index:9999;pointer-events:none;`; + document.body.appendChild(t); + setTimeout(() => t.remove(), 2800); +} diff --git a/public/js/map-ui.js b/public/js/map-ui.js index c7a43a9..a951105 100644 --- a/public/js/map-ui.js +++ b/public/js/map-ui.js @@ -2,6 +2,7 @@ import { loadArena } from "./buildings/arena.js?v=4"; import { loadCharacterHouse } from "./buildings/character-house.js?v=2"; import { loadBlackmarket } from "./buildings/blackmarket.js?v=2"; import { loadMine } from "./buildings/mine.js?v=2"; +import { loadGlucksspiel } from "./gaststaette/glucksspiel.js"; const popup = document.getElementById("building-popup"); const title = document.getElementById("popup-title"); const tooltip = document.getElementById("map-tooltip"); @@ -21,6 +22,7 @@ document.querySelectorAll(".gaststaette-tab").forEach((tab) => { document.querySelectorAll(".gaststaette-tab-content").forEach((c) => c.classList.remove("active")); tab.classList.add("active"); document.getElementById(tab.dataset.tab).classList.add("active"); + if (tab.dataset.tab === "gs-tab1") loadGlucksspiel(); }); }); @@ -181,6 +183,7 @@ document.querySelectorAll(".building").forEach((building) => { gaststaettePopup.style.transform = "translate(-50%, -50%) scale(1)"; gaststaettePopup.classList.add("active"); qmOverlay.classList.add("active"); + loadGlucksspiel(); return; } diff --git a/views/launcher.ejs b/views/launcher.ejs index bca2c5e..3f52dec 100644 --- a/views/launcher.ejs +++ b/views/launcher.ejs @@ -798,21 +798,15 @@
- +
-
-

Inhalt Dummy 1

-
-
-

Inhalt Dummy 2

-
-
-

Inhalt Dummy 3

-
+
+

Inhalt folgt...

+

Inhalt folgt...