hj,c
This commit is contained in:
parent
7b999906fb
commit
c6234d953d
@ -3,8 +3,8 @@ const CARDS_PER_PAGE = 18;
|
|||||||
let currentGroupId = null;
|
let currentGroupId = null;
|
||||||
let currentPage = 1;
|
let currentPage = 1;
|
||||||
let currentDeckId = null;
|
let currentDeckId = null;
|
||||||
let deckCards = []; // [{card_id, level, amount}]
|
let deckCards = []; // [{card_id, amount}]
|
||||||
let userCardsCache = []; // aktuelle Seite: [{card_id, level, amount, name, image, attack, defense}]
|
let userCardsCache = []; // aktuelle Seite: [{card_id, amount, name, image, rarity, attack, defense}]
|
||||||
let decks = []; // [{id, name}]
|
let decks = []; // [{id, name}]
|
||||||
|
|
||||||
|
|
||||||
@ -19,10 +19,12 @@ const RARITY_CRYSTALS = {
|
|||||||
"7": "pinker-cristal.png",
|
"7": "pinker-cristal.png",
|
||||||
};
|
};
|
||||||
|
|
||||||
function rarityImg(rarity, size = 16) {
|
function rarityImgs(rarity, size = 14) {
|
||||||
const file = RARITY_CRYSTALS[String(rarity)];
|
const file = RARITY_CRYSTALS[String(rarity)];
|
||||||
if (!file) return "";
|
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; }
|
.kd-deck-count-indeck { font-family: "Cinzel", serif; font-size: 13px; color: #88cc44; font-weight: bold; }
|
||||||
|
|
||||||
/* ── Rarity Kristall ────────────────────── */
|
/* ── Rarity Kristalle ───────────────────── */
|
||||||
.kd-rarity {
|
.kd-rarity {
|
||||||
position: absolute;
|
display: flex;
|
||||||
top: 3px;
|
justify-content: center;
|
||||||
right: 4px;
|
align-items: center;
|
||||||
z-index: 5;
|
flex-wrap: wrap;
|
||||||
pointer-events: none;
|
gap: 1px;
|
||||||
line-height: 0;
|
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 ──────────────── */
|
/* ── Empty / Loading States ──────────────── */
|
||||||
@ -612,18 +619,18 @@ async function addCardToDeck(card) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level > 5: max. 1 Exemplar im Deck
|
// Rarity > 5: max. 1 Exemplar im Deck
|
||||||
if (card.level > 5) {
|
if (parseInt(card.rarity) > 5) {
|
||||||
const already = deckCards.find(c => c.card_id === card.card_id && c.level === card.level);
|
const already = deckCards.find(c => c.card_id === card.card_id);
|
||||||
if (already) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nicht mehr als vorhanden besitzt
|
// Nicht mehr als vorhanden besitzt
|
||||||
const owned = card.amount;
|
const owned = card.amount;
|
||||||
const inDeck = getDeckCount(card.card_id, card.level);
|
const inDeck = getDeckCount(card.card_id);
|
||||||
if (inDeck >= owned) {
|
if (inDeck >= owned) {
|
||||||
showToast("Du hast keine weiteren Exemplare dieser Karte.");
|
showToast("Du hast keine weiteren Exemplare dieser Karte.");
|
||||||
return;
|
return;
|
||||||
@ -633,7 +640,7 @@ async function addCardToDeck(card) {
|
|||||||
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
|
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
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) {
|
if (!res.ok) {
|
||||||
const err = await res.json();
|
const err = await res.json();
|
||||||
@ -655,7 +662,7 @@ async function removeCardFromDeck(card) {
|
|||||||
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
|
const res = await fetch(`/api/decks/${currentDeckId}/cards`, {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
headers: { "Content-Type": "application/json" },
|
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) {
|
if (!res.ok) {
|
||||||
const err = await res.json();
|
const err = await res.json();
|
||||||
@ -712,14 +719,13 @@ function renderCollectionGrid(grid, cards) {
|
|||||||
const totalInDeck = deckCards.reduce((s, c) => s + c.amount, 0);
|
const totalInDeck = deckCards.reduce((s, c) => s + c.amount, 0);
|
||||||
|
|
||||||
grid.innerHTML = cards.map(c => {
|
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);
|
const maxed = isMaxedOut(c, inDeck, totalInDeck);
|
||||||
return `
|
return `
|
||||||
<div class="kd-card ${maxed ? "kd-card-maxed" : ""}"
|
<div class="kd-card ${maxed ? "kd-card-maxed" : ""}"
|
||||||
data-card-id="${c.card_id}" data-level="${c.level}"
|
data-card-id="${c.card_id}"
|
||||||
title="${c.name} (Level ${c.level})">
|
title="${c.name}">
|
||||||
<div class="kd-card-name">${c.name}</div>
|
<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}"
|
<img src="/images/cards/${c.image}" alt="${c.name}"
|
||||||
onerror="this.src='/images/avatar_placeholder.svg'">
|
onerror="this.src='/images/avatar_placeholder.svg'">
|
||||||
${c.attack != null ? `<span class="kd-stat-atk">${c.attack}</span>` : ""}
|
${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-owned" title="Besitzt du">${c.amount}×</span>
|
||||||
<span class="kd-count-deck" title="Im Deck">🃏 ${inDeck}</span>
|
<span class="kd-count-deck" title="Im Deck">🃏 ${inDeck}</span>
|
||||||
</div>
|
</div>
|
||||||
|
${c.rarity ? `<div class="kd-rarity">${rarityImgs(c.rarity, 13)}</div>` : ""}
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
||||||
grid.querySelectorAll(".kd-card:not(.kd-card-maxed)").forEach(el => {
|
grid.querySelectorAll(".kd-card:not(.kd-card-maxed)").forEach(el => {
|
||||||
el.addEventListener("click", async () => {
|
el.addEventListener("click", async () => {
|
||||||
const card = cards.find(
|
const card = cards.find(c => c.card_id === parseInt(el.dataset.cardId));
|
||||||
c => c.card_id === parseInt(el.dataset.cardId) && c.level === parseInt(el.dataset.level)
|
|
||||||
);
|
|
||||||
if (card) await addCardToDeck(card);
|
if (card) await addCardToDeck(card);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -751,11 +756,10 @@ function renderDeckGrid(grid, cards) {
|
|||||||
}
|
}
|
||||||
// Owned-Anzahl aus userCardsCache auslesen
|
// Owned-Anzahl aus userCardsCache auslesen
|
||||||
grid.innerHTML = cards.map(c => {
|
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 : "?";
|
const ownedAmt = ownedEntry ? ownedEntry.amount : "?";
|
||||||
return `
|
return `
|
||||||
<div class="kd-deck-card" data-card-id="${c.card_id}" data-level="${c.level}" title="Klicken zum Entfernen: ${c.name}">
|
<div class="kd-deck-card" data-card-id="${c.card_id}" title="Klicken zum Entfernen: ${c.name}">
|
||||||
${c.rarity ? `<span class="kd-rarity">${rarityImg(c.rarity, 18)}</span>` : ""}
|
|
||||||
<img src="/images/cards/${c.image}" alt="${c.name}"
|
<img src="/images/cards/${c.image}" alt="${c.name}"
|
||||||
onerror="this.src='/images/avatar_placeholder.svg'">
|
onerror="this.src='/images/avatar_placeholder.svg'">
|
||||||
<div class="kd-deck-card-footer">
|
<div class="kd-deck-card-footer">
|
||||||
@ -764,14 +768,13 @@ function renderDeckGrid(grid, cards) {
|
|||||||
<div class="kd-deck-card-footer-counts">
|
<div class="kd-deck-card-footer-counts">
|
||||||
<span class="kd-deck-count-indeck" title="Im Deck">🃏 ${c.amount}</span>
|
<span class="kd-deck-count-indeck" title="Im Deck">🃏 ${c.amount}</span>
|
||||||
</div>
|
</div>
|
||||||
|
${c.rarity ? `<div class="kd-rarity">${rarityImgs(c.rarity, 14)}</div>` : ""}
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
||||||
grid.querySelectorAll(".kd-deck-card").forEach(el => {
|
grid.querySelectorAll(".kd-deck-card").forEach(el => {
|
||||||
el.addEventListener("click", async () => {
|
el.addEventListener("click", async () => {
|
||||||
const card = cards.find(
|
const card = cards.find(c => c.card_id === parseInt(el.dataset.cardId));
|
||||||
c => c.card_id === parseInt(el.dataset.cardId) && c.level === parseInt(el.dataset.level)
|
|
||||||
);
|
|
||||||
if (card) await removeCardFromDeck(card);
|
if (card) await removeCardFromDeck(card);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -878,16 +881,16 @@ function showNewDeckModal() {
|
|||||||
/* ══════════════════════════════════════════════
|
/* ══════════════════════════════════════════════
|
||||||
HELPERS
|
HELPERS
|
||||||
══════════════════════════════════════════════ */
|
══════════════════════════════════════════════ */
|
||||||
function getDeckCount(cardId, level) {
|
function getDeckCount(cardId) {
|
||||||
const entry = deckCards.find(c => c.card_id === cardId && c.level === level);
|
const entry = deckCards.find(c => c.card_id === cardId);
|
||||||
return entry ? entry.amount : 0;
|
return entry ? entry.amount : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMaxedOut(card, inDeck, totalInDeck) {
|
function isMaxedOut(card, inDeck, totalInDeck) {
|
||||||
if (!currentDeckId) return false;
|
if (!currentDeckId) return false;
|
||||||
if (totalInDeck >= 30) return true;
|
if (totalInDeck >= 30) return true;
|
||||||
if (inDeck >= card.amount) return true; // mehr als besessen
|
if (inDeck >= card.amount) return true; // mehr als besessen
|
||||||
if (card.level > 5 && inDeck >= 1) return true; // Level > 5: nur 1x
|
if (parseInt(card.rarity) > 5 && inDeck >= 1) return true; // Rarity > 5: nur 1x
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -73,7 +73,6 @@ router.get("/user-cards", async (req, res) => {
|
|||||||
const [cards] = await db.query(
|
const [cards] = await db.query(
|
||||||
`SELECT
|
`SELECT
|
||||||
uc.card_id,
|
uc.card_id,
|
||||||
uc.level,
|
|
||||||
uc.amount,
|
uc.amount,
|
||||||
c.name,
|
c.name,
|
||||||
c.image,
|
c.image,
|
||||||
@ -86,9 +85,9 @@ router.get("/user-cards", async (req, res) => {
|
|||||||
FROM user_cards uc
|
FROM user_cards uc
|
||||||
JOIN cards c ON c.id = uc.card_id
|
JOIN cards c ON c.id = uc.card_id
|
||||||
JOIN card_groups cg ON cg.id = c.group_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 = ?
|
WHERE uc.user_id = ? AND c.group_id = ?
|
||||||
ORDER BY c.id, uc.level
|
ORDER BY c.id
|
||||||
LIMIT ? OFFSET ?`,
|
LIMIT ? OFFSET ?`,
|
||||||
[userId, group_id, parseInt(limit), parseInt(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(
|
const [cards] = await db.query(
|
||||||
`SELECT
|
`SELECT
|
||||||
dc.card_id,
|
dc.card_id,
|
||||||
dc.level,
|
|
||||||
dc.amount,
|
dc.amount,
|
||||||
c.name,
|
c.name,
|
||||||
c.image,
|
c.image,
|
||||||
@ -195,9 +193,9 @@ router.get("/decks/:id/cards", async (req, res) => {
|
|||||||
cl.defense
|
cl.defense
|
||||||
FROM deck_cards dc
|
FROM deck_cards dc
|
||||||
JOIN cards c ON c.id = dc.card_id
|
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 = ?
|
WHERE dc.deck_id = ?
|
||||||
ORDER BY c.name, dc.level`,
|
ORDER BY c.name`,
|
||||||
[deckId]
|
[deckId]
|
||||||
);
|
);
|
||||||
res.json(cards);
|
res.json(cards);
|
||||||
@ -215,7 +213,7 @@ router.get("/decks/:id/cards", async (req, res) => {
|
|||||||
router.post("/decks/:id/cards", async (req, res) => {
|
router.post("/decks/:id/cards", async (req, res) => {
|
||||||
const userId = req.session.user.id;
|
const userId = req.session.user.id;
|
||||||
const deckId = req.params.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." });
|
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." });
|
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(
|
const [[owned]] = await db.query(
|
||||||
"SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ? AND level = ?",
|
"SELECT amount FROM user_cards WHERE user_id = ? AND card_id = ?",
|
||||||
[userId, card_id, level]
|
[userId, card_id]
|
||||||
);
|
);
|
||||||
if (!owned) return res.status(400).json({ error: "Du besitzt diese Karte nicht." });
|
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)." });
|
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(
|
const [[existing]] = await db.query(
|
||||||
"SELECT amount FROM deck_cards WHERE deck_id = ? AND card_id = ? AND level = ?",
|
"SELECT amount FROM deck_cards WHERE deck_id = ? AND card_id = ?",
|
||||||
[deckId, card_id, level]
|
[deckId, card_id]
|
||||||
);
|
);
|
||||||
|
|
||||||
if (level > 5 && existing) {
|
if (parseInt(cardInfo?.rarity) > 5 && existing) {
|
||||||
return res.status(400).json({ error: "Karten ab Level 6 dürfen nur einmal im Deck sein." });
|
return res.status(400).json({ error: "Karten ab Rarity 6 dürfen nur einmal im Deck sein." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nicht mehr einfügen als besessen
|
// Nicht mehr einfügen als besessen
|
||||||
@ -262,13 +264,13 @@ router.post("/decks/:id/cards", async (req, res) => {
|
|||||||
// Einfügen oder erhöhen
|
// Einfügen oder erhöhen
|
||||||
if (existing) {
|
if (existing) {
|
||||||
await db.query(
|
await db.query(
|
||||||
"UPDATE deck_cards SET amount = amount + 1 WHERE deck_id = ? AND card_id = ? AND level = ?",
|
"UPDATE deck_cards SET amount = amount + 1 WHERE deck_id = ? AND card_id = ?",
|
||||||
[deckId, card_id, level]
|
[deckId, card_id]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await db.query(
|
await db.query(
|
||||||
"INSERT INTO deck_cards (deck_id, card_id, level, amount) VALUES (?, ?, ?, 1)",
|
"INSERT INTO deck_cards (deck_id, card_id, amount) VALUES (?, ?, 1)",
|
||||||
[deckId, card_id, level]
|
[deckId, card_id]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +293,7 @@ router.post("/decks/:id/cards", async (req, res) => {
|
|||||||
router.delete("/decks/:id/cards", async (req, res) => {
|
router.delete("/decks/:id/cards", async (req, res) => {
|
||||||
const userId = req.session.user.id;
|
const userId = req.session.user.id;
|
||||||
const deckId = req.params.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." });
|
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." });
|
if (!deck) return res.status(404).json({ error: "Deck nicht gefunden." });
|
||||||
|
|
||||||
const [[entry]] = await db.query(
|
const [[entry]] = await db.query(
|
||||||
"SELECT id, amount FROM deck_cards WHERE deck_id = ? AND card_id = ? AND level = ?",
|
"SELECT id, amount FROM deck_cards WHERE deck_id = ? AND card_id = ?",
|
||||||
[deckId, card_id, level]
|
[deckId, card_id]
|
||||||
);
|
);
|
||||||
if (!entry) return res.status(404).json({ error: "Karte nicht im Deck." });
|
if (!entry) return res.status(404).json({ error: "Karte nicht im Deck." });
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user