This commit is contained in:
cay 2026-04-04 10:28:05 +01:00
parent 7b999906fb
commit c6234d953d
2 changed files with 62 additions and 57 deletions

View File

@ -3,8 +3,8 @@ const CARDS_PER_PAGE = 18;
let currentGroupId = null;
let currentPage = 1;
let currentDeckId = null;
let deckCards = []; // [{card_id, level, amount}]
let userCardsCache = []; // aktuelle Seite: [{card_id, level, amount, name, image, attack, defense}]
let deckCards = []; // [{card_id, amount}]
let userCardsCache = []; // aktuelle Seite: [{card_id, amount, name, image, rarity, attack, defense}]
let decks = []; // [{id, name}]
@ -19,10 +19,12 @@ const RARITY_CRYSTALS = {
"7": "pinker-cristal.png",
};
function rarityImg(rarity, size = 16) {
function rarityImgs(rarity, size = 14) {
const file = RARITY_CRYSTALS[String(rarity)];
if (!file) return "";
return `<img src="/images/items/${file}" alt="Stufe ${rarity}" style="width:${size}px;height:${size}px;object-fit:contain;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.8));">`;
const count = parseInt(rarity) || 0;
const img = `<img src="/images/items/${file}" alt="Stufe ${rarity}" style="width:${size}px;height:${size}px;object-fit:contain;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.8));">`;
return img.repeat(count);
}
/*
@ -415,14 +417,19 @@ function renderShell() {
}
.kd-deck-count-indeck { font-family: "Cinzel", serif; font-size: 13px; color: #88cc44; font-weight: bold; }
/* ── Rarity Kristall ───────────────────── */
/* ── Rarity Kristalle ───────────────────── */
.kd-rarity {
position: absolute;
top: 3px;
right: 4px;
z-index: 5;
pointer-events: none;
line-height: 0;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 1px;
padding: 3px 4px;
background: rgba(0,0,0,0.82);
border-top: 1px solid #3a2810;
flex-shrink: 0;
min-height: 20px;
border-radius: 0 0 6px 6px;
}
/* ── Empty / Loading States ──────────────── */
@ -612,18 +619,18 @@ async function addCardToDeck(card) {
return;
}
// Level > 5: max. 1 Exemplar im Deck
if (card.level > 5) {
const already = deckCards.find(c => c.card_id === card.card_id && c.level === card.level);
// Rarity > 5: max. 1 Exemplar im Deck
if (parseInt(card.rarity) > 5) {
const already = deckCards.find(c => c.card_id === card.card_id);
if (already) {
showToast("Karten ab Level 6 dürfen nur 1x im Deck sein.");
showToast("Karten ab Rarity 6 dürfen nur 1x im Deck sein.");
return;
}
}
// Nicht mehr als vorhanden besitzt
const owned = card.amount;
const inDeck = getDeckCount(card.card_id, card.level);
const inDeck = getDeckCount(card.card_id);
if (inDeck >= owned) {
showToast("Du hast keine weiteren Exemplare dieser Karte.");
return;
@ -633,7 +640,7 @@ async function addCardToDeck(card) {
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ card_id: card.card_id, level: card.level }),
body: JSON.stringify({ card_id: card.card_id }),
});
if (!res.ok) {
const err = await res.json();
@ -655,7 +662,7 @@ async function removeCardFromDeck(card) {
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ card_id: card.card_id, level: card.level }),
body: JSON.stringify({ card_id: card.card_id }),
});
if (!res.ok) {
const err = await res.json();
@ -712,14 +719,13 @@ function renderCollectionGrid(grid, cards) {
const totalInDeck = deckCards.reduce((s, c) => s + c.amount, 0);
grid.innerHTML = cards.map(c => {
const inDeck = getDeckCount(c.card_id, c.level);
const inDeck = getDeckCount(c.card_id);
const maxed = isMaxedOut(c, inDeck, totalInDeck);
return `
<div class="kd-card ${maxed ? "kd-card-maxed" : ""}"
data-card-id="${c.card_id}" data-level="${c.level}"
title="${c.name} (Level ${c.level})">
data-card-id="${c.card_id}"
title="${c.name}">
<div class="kd-card-name">${c.name}</div>
${c.rarity ? `<span class="kd-rarity">${rarityImg(c.rarity, 15)}</span>` : ""}
<img src="/images/cards/${c.image}" alt="${c.name}"
onerror="this.src='/images/avatar_placeholder.svg'">
${c.attack != null ? `<span class="kd-stat-atk">${c.attack}</span>` : ""}
@ -728,14 +734,13 @@ function renderCollectionGrid(grid, cards) {
<span class="kd-count-owned" title="Besitzt du">${c.amount}×</span>
<span class="kd-count-deck" title="Im Deck">🃏 ${inDeck}</span>
</div>
${c.rarity ? `<div class="kd-rarity">${rarityImgs(c.rarity, 13)}</div>` : ""}
</div>`;
}).join("");
grid.querySelectorAll(".kd-card:not(.kd-card-maxed)").forEach(el => {
el.addEventListener("click", async () => {
const card = cards.find(
c => c.card_id === parseInt(el.dataset.cardId) && c.level === parseInt(el.dataset.level)
);
const card = cards.find(c => c.card_id === parseInt(el.dataset.cardId));
if (card) await addCardToDeck(card);
});
});
@ -751,11 +756,10 @@ function renderDeckGrid(grid, cards) {
}
// Owned-Anzahl aus userCardsCache auslesen
grid.innerHTML = cards.map(c => {
const ownedEntry = userCardsCache.find(u => u.card_id === c.card_id && u.level === c.level);
const ownedEntry = userCardsCache.find(u => u.card_id === c.card_id);
const ownedAmt = ownedEntry ? ownedEntry.amount : "?";
return `
<div class="kd-deck-card" data-card-id="${c.card_id}" data-level="${c.level}" title="Klicken zum Entfernen: ${c.name}">
${c.rarity ? `<span class="kd-rarity">${rarityImg(c.rarity, 18)}</span>` : ""}
<div class="kd-deck-card" data-card-id="${c.card_id}" title="Klicken zum Entfernen: ${c.name}">
<img src="/images/cards/${c.image}" alt="${c.name}"
onerror="this.src='/images/avatar_placeholder.svg'">
<div class="kd-deck-card-footer">
@ -764,14 +768,13 @@ function renderDeckGrid(grid, cards) {
<div class="kd-deck-card-footer-counts">
<span class="kd-deck-count-indeck" title="Im Deck">🃏 ${c.amount}</span>
</div>
${c.rarity ? `<div class="kd-rarity">${rarityImgs(c.rarity, 14)}</div>` : ""}
</div>`;
}).join("");
grid.querySelectorAll(".kd-deck-card").forEach(el => {
el.addEventListener("click", async () => {
const card = cards.find(
c => c.card_id === parseInt(el.dataset.cardId) && c.level === parseInt(el.dataset.level)
);
const card = cards.find(c => c.card_id === parseInt(el.dataset.cardId));
if (card) await removeCardFromDeck(card);
});
});
@ -878,16 +881,16 @@ function showNewDeckModal() {
/*
HELPERS
*/
function getDeckCount(cardId, level) {
const entry = deckCards.find(c => c.card_id === cardId && c.level === level);
function getDeckCount(cardId) {
const entry = deckCards.find(c => c.card_id === cardId);
return entry ? entry.amount : 0;
}
function isMaxedOut(card, inDeck, totalInDeck) {
if (!currentDeckId) return false;
if (totalInDeck >= 30) return true;
if (inDeck >= card.amount) return true; // mehr als besessen
if (card.level > 5 && inDeck >= 1) return true; // Level > 5: nur 1x
if (inDeck >= card.amount) return true; // mehr als besessen
if (parseInt(card.rarity) > 5 && inDeck >= 1) return true; // Rarity > 5: nur 1x
return false;
}

View File

@ -73,7 +73,6 @@ router.get("/user-cards", async (req, res) => {
const [cards] = await db.query(
`SELECT
uc.card_id,
uc.level,
uc.amount,
c.name,
c.image,
@ -86,9 +85,9 @@ router.get("/user-cards", async (req, res) => {
FROM user_cards uc
JOIN cards c ON c.id = uc.card_id
JOIN card_groups cg ON cg.id = c.group_id
LEFT JOIN card_levels cl ON cl.card_id = uc.card_id AND cl.level = uc.level
LEFT JOIN card_levels cl ON cl.card_id = uc.card_id AND cl.level = 1
WHERE uc.user_id = ? AND c.group_id = ?
ORDER BY c.id, uc.level
ORDER BY c.id
LIMIT ? OFFSET ?`,
[userId, group_id, parseInt(limit), parseInt(offset)]
);
@ -186,7 +185,6 @@ router.get("/decks/:id/cards", async (req, res) => {
const [cards] = await db.query(
`SELECT
dc.card_id,
dc.level,
dc.amount,
c.name,
c.image,
@ -195,9 +193,9 @@ router.get("/decks/:id/cards", async (req, res) => {
cl.defense
FROM deck_cards dc
JOIN cards c ON c.id = dc.card_id
LEFT JOIN card_levels cl ON cl.card_id = dc.card_id AND cl.level = dc.level
LEFT JOIN card_levels cl ON cl.card_id = dc.card_id AND cl.level = 1
WHERE dc.deck_id = ?
ORDER BY c.name, dc.level`,
ORDER BY c.name`,
[deckId]
);
res.json(cards);
@ -215,7 +213,7 @@ router.get("/decks/:id/cards", async (req, res) => {
router.post("/decks/:id/cards", async (req, res) => {
const userId = req.session.user.id;
const deckId = req.params.id;
const { card_id, level = 1 } = req.body;
const { card_id } = req.body;
if (!card_id) return res.status(400).json({ error: "card_id fehlt." });
@ -227,10 +225,10 @@ router.post("/decks/:id/cards", async (req, res) => {
);
if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." });
// Besitzt der Spieler diese Karte mit diesem Level?
// Besitzt der Spieler diese Karte?
const [[owned]] = await db.query(
"SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ? AND level = ?",
[userId, card_id, level]
"SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ?",
[userId, card_id]
);
if (!owned) return res.status(400).json({ error: "Du besitzt diese Karte nicht." });
@ -243,14 +241,18 @@ router.post("/decks/:id/cards", async (req, res) => {
return res.status(400).json({ error: "Deck ist voll (max. 30 Karten)." });
}
// Level > 5: max. 1× im Deck
// Rarity > 5: max. 1× im Deck
const [[cardInfo]] = await db.query(
"SELECT rarity FROM cards WHERE id = ?",
[card_id]
);
const [[existing]] = await db.query(
"SELECT amount FROM deck_cards WHERE deck_id = ? AND card_id = ? AND level = ?",
[deckId, card_id, level]
"SELECT amount FROM deck_cards WHERE deck_id = ? AND card_id = ?",
[deckId, card_id]
);
if (level > 5 && existing) {
return res.status(400).json({ error: "Karten ab Level 6 dürfen nur einmal im Deck sein." });
if (parseInt(cardInfo?.rarity) > 5 && existing) {
return res.status(400).json({ error: "Karten ab Rarity 6 dürfen nur einmal im Deck sein." });
}
// Nicht mehr einfügen als besessen
@ -262,13 +264,13 @@ router.post("/decks/:id/cards", async (req, res) => {
// Einfügen oder erhöhen
if (existing) {
await db.query(
"UPDATE deck_cards SET amount = amount + 1 WHERE deck_id = ? AND card_id = ? AND level = ?",
[deckId, card_id, level]
"UPDATE deck_cards SET amount = amount + 1 WHERE deck_id = ? AND card_id = ?",
[deckId, card_id]
);
} else {
await db.query(
"INSERT INTO deck_cards (deck_id, card_id, level, amount) VALUES (?, ?, ?, 1)",
[deckId, card_id, level]
"INSERT INTO deck_cards (deck_id, card_id, amount) VALUES (?, ?, 1)",
[deckId, card_id]
);
}
@ -291,7 +293,7 @@ router.post("/decks/:id/cards", async (req, res) => {
router.delete("/decks/:id/cards", async (req, res) => {
const userId = req.session.user.id;
const deckId = req.params.id;
const { card_id, level = 1 } = req.body;
const { card_id } = req.body;
if (!card_id) return res.status(400).json({ error: "card_id fehlt." });
@ -304,8 +306,8 @@ router.delete("/decks/:id/cards", async (req, res) => {
if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." });
const [[entry]] = await db.query(
"SELECT id, amount FROM deck_cards WHERE deck_id = ? AND card_id = ? AND level = ?",
[deckId, card_id, level]
"SELECT id, amount FROM deck_cards WHERE deck_id = ? AND card_id = ?",
[deckId, card_id]
);
if (!entry) return res.status(404).json({ error: "Karte nicht im Deck." });