// Fantabeto PWA — Auction Assistant const API = "/api"; const state = { tab: "auction", players: [], roster: [], budgetSpent: 0, budgetTotal: 500, selectedPlayer: null, search: "", roleFilter: "", bidAmount: 0, online: true, }; // ── Init ──────────────────────────────────────────────────────── async function init() { registerSW(); await loadRoster(); await searchPlayers(); await checkHealth(); setInterval(checkHealth, 30000); } function registerSW() { if ("serviceWorker" in navigator) { navigator.serviceWorker.register("/sw.js"); } } async function api(url, opts = {}) { try { const res = await fetch(url, opts); state.online = true; updatePing(); return await res.json(); } catch (e) { state.online = false; updatePing(); return null; } } async function checkHealth() { const r = await api(API + "/stats"); document.getElementById("ping").className = state.online ? "ping" : "ping offline"; document.getElementById("ping").textContent = state.online ? "● connected" : "○ offline"; } function updatePing() { const el = document.getElementById("ping"); if (!el) return; el.className = state.online ? "ping" : "ping offline"; el.textContent = state.online ? "● connected" : "○ offline"; } // ── Search ────────────────────────────────────────────────────── async function searchPlayers() { const params = new URLSearchParams({ limit: 60, sort: "projected_points" }); if (state.search) params.set("search", state.search); if (state.roleFilter) params.set("role", state.roleFilter); const data = await api(API + "/players?" + params); state.players = data?.players || []; renderPlayerList(); } // ── Roster ────────────────────────────────────────────────────── async function loadRoster() { const data = await api(API + "/roster"); if (data) { state.roster = data.roster || []; state.budgetSpent = data.budget_spent || 0; } renderBudget(); renderRoster(); } async function addToRoster(name, price) { const data = await api(API + "/roster/add", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, price }), }); if (data?.error) { toast(data.error, "error"); return false; } if (data?.ok) { state.roster = data.roster.roster; state.budgetSpent = data.roster.budget_spent; renderBudget(); renderRoster(); renderPlayerList(); toast(`${name} added · ${price} cr`); return true; } return false; } async function removeFromRoster(name) { const data = await api(API + "/roster/remove", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); if (data?.ok) { state.roster = data.roster.roster; state.budgetSpent = data.roster.budget_spent; renderBudget(); renderRoster(); renderPlayerList(); toast(`Removed ${name}`); } } async function resetRoster() { if (!confirm("Reset entire roster?")) return; await api(API + "/roster/reset", { method: "POST" }); state.roster = []; state.budgetSpent = 0; renderBudget(); renderRoster(); renderPlayerList(); } // ── Player detail ─────────────────────────────────────────────── async function showPlayer(name) { const data = await api(API + "/player/" + encodeURIComponent(name)); if (!data) return; state.selectedPlayer = data; state.bidAmount = data.recommended_bid || 0; renderDetail(); } function closeDetail() { state.selectedPlayer = null; renderDetail(); } function setBid(amount) { state.bidAmount = amount; document.getElementById("bidInput").value = amount; document.querySelectorAll(".bid-quick button").forEach((b) => { const v = parseInt(b.dataset.bid); b.className = v === amount ? "active" : ""; }); } function formatBidButtons(player) { const bids = []; const rec = player.recommended_bid || 0; const max = player.max_bid || 0; const mkt = player.market_value || 0; if (mkt > 0) bids.push(mkt); if (rec > 0 && rec !== mkt) bids.push(rec); if (max > 0 && max !== rec && max !== mkt) bids.push(max); // Ensure at least 3 options if (bids.length < 3 && mkt > 10) bids.push(Math.round(mkt * 1.3)); return [...new Set(bids)].sort((a, b) => a - b); } // ── Render ────────────────────────────────────────────────────── function renderBudget() { const spent = state.budgetSpent; const total = state.budgetTotal; const pct = Math.min((spent / total) * 100, 100); document.getElementById("budgetSpent").textContent = spent; document.getElementById("budgetRemaining").textContent = total - spent; document.getElementById("budgetFill").style.width = pct + "%"; document.getElementById("budgetFill").style.background = pct > 90 ? "var(--red)" : pct > 70 ? "var(--gold)" : "var(--green)"; } function renderPlayerList() { const el = document.getElementById("playerList"); const inRoster = new Set(state.roster.map((p) => p.name)); if (!state.players.length) { el.innerHTML = '