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 = `
`;
+ 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 `
+
+
+
+
+
+
+
+
+
+
+
+
+ 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.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) => `
+
+

+
+ `).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 @@
-
+