dok/public/js/buildings/bazaar.js
2026-04-11 14:56:30 +01:00

268 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
public/js/buildings/bazaar.js
============================================================ */
const BAZAAR_PER_PAGE = 18;
let baz_initialized = false;
let baz_page = 1;
let baz_wood = 0, baz_iron = 0, baz_gold = 0, baz_gems = 0;
const BAZ_SVG_RANGE = `<svg viewBox="0 0 16 16" width="10" height="10" style="display:inline;vertical-align:middle;flex-shrink:0" fill="none" stroke="#e8b84b" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 2 Q1 8 4 14"/><line x1="4" y1="2" x2="4" y2="14" stroke-width="0.7" stroke-dasharray="2,1.5"/><line x1="4" y1="8" x2="13" y2="8"/><polyline points="11,6 13,8 11,10"/><line x1="5" y1="7" x2="4" y2="8"/><line x1="5" y1="9" x2="4" y2="8"/></svg>`;
const BAZ_SVG_RACE = `<svg viewBox="0 0 16 16" width="10" height="10" style="display:inline;vertical-align:middle;flex-shrink:0" fill="none" stroke="#7de87d" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="2.5" r="1.4" fill="#7de87d" stroke="none"/><line x1="9" y1="4" x2="8" y2="9"/><line x1="8" y1="9" x2="10" y2="14"/><line x1="8" y1="9" x2="6" y2="13"/><line x1="8.5" y1="5.5" x2="11" y2="8"/><line x1="8.5" y1="5.5" x2="6" y2="7"/></svg>`;
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=11) {
const file = RARITY_CRYSTALS[String(rarity)];
if (!file) return "";
const img = `<img src="/images/items/${file}" alt="${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(parseInt(rarity)||0);
}
function loadCSS() {
if (!document.querySelector('link[href="/css/bazaar.css"]')) {
const l = document.createElement("link");
l.rel = "stylesheet"; l.href = "/css/bazaar.css";
document.head.appendChild(l);
}
}
function ensurePopup() {
if (document.getElementById("bazaar-popup")) return;
const popup = document.createElement("div");
popup.id = "bazaar-popup";
popup.className = "qm-popup";
popup.innerHTML = `
<div class="qm-popup-header">
<span class="qm-popup-title">Bazaar</span>
<div class="baz-header-right">
<span class="baz-currency">🪵 <span id="baz-wood"></span></span>
<span class="baz-currency">⚙️ <span id="baz-iron"></span></span>
<span class="baz-currency">🪙 <span id="baz-gold"></span></span>
<span class="baz-currency">💎 <span id="baz-gems"></span></span>
<span class="qm-popup-close" id="bazaar-close">✕</span>
</div>
</div>
<div class="mp-body-wrap">
<aside class="mp-tabs" id="baz-tabs">
<button class="mp-tab mp-tab-active" data-tab="baz-panel-1">
<span class="mp-tab-dot"></span><span>Händler</span>
</button>
<button class="mp-tab" data-tab="baz-panel-2">
<span class="mp-tab-dot"></span><span>Auktionen</span>
</button>
<button class="mp-tab" data-tab="baz-panel-3">
<span class="mp-tab-dot"></span><span>Tauschbörse</span>
</button>
</aside>
<div class="mp-content">
<div class="mp-panel active" id="baz-panel-1">
<div class="mp-col-header">Händler</div>
<div class="baz-grid" id="baz-grid"><div class="baz-loading">Lade Karten…</div></div>
<div class="baz-pagination" id="baz-pagination"></div>
</div>
<div class="mp-panel" id="baz-panel-2">
<div class="mp-col-header">Auktionen</div>
<div class="mp-panel-body"><span class="mp-coming-soon">Demnächst verfügbar…</span></div>
</div>
<div class="mp-panel" id="baz-panel-3">
<div class="mp-col-header">Tauschbörse</div>
<div class="mp-panel-body"><span class="mp-coming-soon">Demnächst verfügbar…</span></div>
</div>
</div>
</div>`;
document.body.appendChild(popup);
popup.querySelector("#bazaar-close").addEventListener("click", closeBazaar);
popup.querySelectorAll(".mp-tab").forEach((btn) => {
btn.addEventListener("click", () => {
popup.querySelectorAll(".mp-tab").forEach(t => t.classList.remove("mp-tab-active"));
popup.querySelectorAll(".mp-panel").forEach(p => p.classList.remove("active"));
btn.classList.add("mp-tab-active");
document.getElementById(btn.dataset.tab)?.classList.add("active");
});
});
/* Drag */
const header = popup.querySelector(".qm-popup-header");
let dragging=false, sx,sy,sl,st;
header.style.cursor="grab";
header.addEventListener("mousedown",(e)=>{
if(e.target.classList.contains("qm-popup-close"))return;
dragging=true; header.style.cursor="grabbing";
const r=popup.getBoundingClientRect();
sx=e.clientX;sy=e.clientY;sl=r.left;st=r.top;
popup.style.transform="none";
popup.style.left=sl+"px";popup.style.top=st+"px";
e.preventDefault();
});
document.addEventListener("mousemove",(e)=>{
if(!dragging)return;
popup.style.left=(sl+(e.clientX-sx))+"px";
popup.style.top=(st+(e.clientY-sy))+"px";
});
document.addEventListener("mouseup",()=>{dragging=false;header.style.cursor="grab";});
}
function updateCurrencyDisplay() {
const fmt = n => n.toLocaleString("de-DE");
const w=document.getElementById("baz-wood"); if(w) w.textContent=fmt(baz_wood);
const i=document.getElementById("baz-iron"); if(i) i.textContent=fmt(baz_iron);
const g=document.getElementById("baz-gold"); if(g) g.textContent=fmt(baz_gold);
const d=document.getElementById("baz-gems"); if(d) d.textContent=fmt(baz_gems);
}
async function loadShopCards() {
const grid = document.getElementById("baz-grid");
const pagination = document.getElementById("baz-pagination");
if (!grid) return;
grid.innerHTML = `<div class="baz-loading">Lade Karten…</div>`;
if (pagination) pagination.innerHTML = "";
try {
const res = await fetch(`/api/bazaar/cards?page=${baz_page}&limit=${BAZAAR_PER_PAGE}`);
if (!res.ok) throw new Error(res.status);
const data = await res.json();
baz_wood=data.wood; baz_iron=data.iron; baz_gold=data.gold; baz_gems=data.gems;
updateCurrencyDisplay();
if (!data.cards.length) {
grid.innerHTML = `<div class="baz-empty">Keine Karten verfügbar.</div>`;
return;
}
grid.innerHTML = data.cards.map((c) => {
const prices = [
c.price_wood > 0 ? `<span class="baz-price-wood">🪵 ${c.price_wood}</span>` : "",
c.price_iron > 0 ? `<span class="baz-price-iron">⚙️ ${c.price_iron}</span>` : "",
c.price_gold > 0 ? `<span class="baz-price-gold">🪙 ${c.price_gold}</span>` : "",
c.price_gems > 0 ? `<span class="baz-price-gems">💎 ${c.price_gems}</span>` : "",
].filter(Boolean).join("");
return `
<div class="baz-card" data-card-id="${c.id}" title="${c.name}">
<img src="/images/cards/${c.image}" alt="${c.name}"
onerror="this.src='/images/avatar_placeholder.svg'">
${c.attack != null ? `<span class="baz-stat-atk">${c.attack}</span>` : ""}
${c.defends != null ? `<span class="baz-stat-def">${c.defends}</span>` : ""}
${c.cooldown != null ? `<span class="baz-stat-cd">${c.cooldown}</span>` : ""}
${c.rarity ? `<div class="baz-rarity">${rarityImgs(c.rarity,11)}</div>` : ""}
${(c.range != null || c.race != null) ? `
<div class="baz-range-race">
${c.range != null ? `<span class="baz-badge-range">${BAZ_SVG_RANGE}&thinsp;${c.range}</span>` : ""}
${c.race != null ? `<span class="baz-badge-race">${BAZ_SVG_RACE}&thinsp;${c.race}</span>` : ""}
</div>` : ""}
<div class="baz-price">${prices || "<span style='color:#8b6a3c;font-size:8px;'>Kostenlos</span>"}</div>
</div>`;
}).join("");
grid.querySelectorAll(".baz-card").forEach((el) => {
el.addEventListener("click", () => {
const card = data.cards.find(c => c.id === parseInt(el.dataset.cardId));
if (card) showBuyConfirm(card);
});
});
renderPagination(pagination, data.totalPages, data.total);
} catch (err) {
grid.innerHTML = `<div class="baz-empty">Fehler beim Laden.</div>`;
console.error("[bazaar]", err);
}
}
function showBuyConfirm(card) {
document.getElementById("baz-confirm-modal")?.remove();
const canAfford =
baz_wood >= card.price_wood &&
baz_iron >= card.price_iron &&
baz_gold >= card.price_gold &&
baz_gems >= card.price_gems;
const priceStr = [
card.price_wood > 0 ? `🪵 ${card.price_wood} Holz` : "",
card.price_iron > 0 ? `⚙️ ${card.price_iron} Eisen` : "",
card.price_gold > 0 ? `🪙 ${card.price_gold} Gold` : "",
card.price_gems > 0 ? `💎 ${card.price_gems} Gems` : "",
].filter(Boolean).join(" ");
const modal = document.createElement("div");
modal.id = "baz-confirm-modal";
modal.innerHTML = `
<div class="baz-confirm-backdrop"></div>
<div class="baz-confirm-box">
<div class="baz-confirm-img-wrap">
<img src="/images/cards/${card.image}" alt="${card.name}"
onerror="this.src='/images/avatar_placeholder.svg'">
</div>
<div class="baz-confirm-name">${card.name}</div>
<div class="baz-confirm-price">${priceStr || "Kostenlos"}</div>
${!canAfford ? `<div class="baz-confirm-warn">⚠ Nicht genug Ressourcen</div>` : ""}
<div class="baz-confirm-btns">
<button class="baz-btn-cancel" id="baz-cancel">Abbrechen</button>
<button class="baz-btn-buy" id="baz-confirm" ${!canAfford?"disabled":""}>Kaufen</button>
</div>
</div>`;
document.getElementById("bazaar-popup").appendChild(modal);
modal.querySelector("#baz-cancel").onclick = () => modal.remove();
modal.querySelector(".baz-confirm-backdrop").onclick = () => modal.remove();
modal.querySelector("#baz-confirm").onclick = async () => {
const btn = modal.querySelector("#baz-confirm");
btn.disabled = true; btn.textContent = "…";
try {
const res = await fetch("/api/bazaar/buy", {
method:"POST", headers:{"Content-Type":"application/json"},
body: JSON.stringify({ card_id: card.id }),
});
const data = await res.json();
if (!res.ok) { btn.textContent = data.error||"Fehler"; setTimeout(()=>modal.remove(),2000); return; }
baz_wood=data.wood; baz_iron=data.iron; baz_gold=data.gold; baz_gems=data.gems;
updateCurrencyDisplay();
modal.remove();
showToast(`${card.name} gekauft!`);
await loadShopCards();
} catch { btn.textContent="Fehler"; setTimeout(()=>modal.remove(),2000); }
};
}
function renderPagination(el, totalPages, total) {
if (!el||!totalPages||totalPages<=1) return;
el.innerHTML = `
<button class="baz-page-btn" id="baz-prev" ${baz_page===1?"disabled":""}>◀</button>
${Array.from({length:totalPages},(_,i)=>i+1).map(p=>
`<button class="baz-page-btn ${p===baz_page?"baz-page-active":""}" data-page="${p}">${p}</button>`
).join("")}
<button class="baz-page-btn" id="baz-next" ${baz_page===totalPages?"disabled":""}></button>
<span class="baz-page-info">${total} Karten</span>`;
el.querySelector("#baz-prev")?.addEventListener("click",async()=>{if(baz_page>1){baz_page--;await loadShopCards();}});
el.querySelector("#baz-next")?.addEventListener("click",async()=>{if(baz_page<totalPages){baz_page++;await loadShopCards();}});
el.querySelectorAll("[data-page]").forEach(btn=>btn.addEventListener("click",async()=>{baz_page=parseInt(btn.dataset.page);await loadShopCards();}));
}
function showToast(msg) {
const t=document.createElement("div"); t.className="baz-toast"; t.textContent=msg;
document.body.appendChild(t); setTimeout(()=>t.remove(),2800);
}
function closeBazaar() {
document.getElementById("bazaar-popup")?.classList.remove("active");
document.getElementById("qm-overlay")?.classList.remove("active");
}
export function loadBazaar() {
loadCSS();
ensurePopup();
const popup=document.getElementById("bazaar-popup");
const overlay=document.getElementById("qm-overlay");
popup.style.left="50%"; popup.style.top="50%";
popup.style.transform="translate(-50%, -50%) scale(1)";
popup.classList.add("active");
overlay?.classList.add("active");
if (!baz_initialized) { baz_initialized=true; baz_page=1; loadShopCards(); }
}