(() => { "use strict"; const qs = (sel) => document.querySelector(sel); const state = { index: null, rows: [], table: null, filters: { episode: "", difficulty: "", section: "", search: "", }, }; function esc(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function labelValue(value) { return String(value || "") .replace(/^Episode(\d+)$/, "Episode $1") .replaceAll("_", " "); } function setStatus(message, kind = "") { const box = qs("#drops-placeholder"); if (!box) return; box.innerHTML = `
${esc(message)}
`; } async function fetchJson(path) { const res = await fetch(path, { cache: "no-store" }); if (!res.ok) throw new Error(`${path}: HTTP ${res.status}`); return res.json(); } function uniqueSorted(rows, key) { return [...new Set(rows.map((row) => row[key]).filter(Boolean))] .sort((a, b) => String(a).localeCompare(String(b))); } function fillSelect(select, values, allLabel) { if (!select) return; const previous = select.value; select.innerHTML = ``; for (const value of values) { const opt = document.createElement("option"); opt.value = value; opt.textContent = labelValue(value); select.appendChild(opt); } if ([...select.options].some((opt) => opt.value === previous)) { select.value = previous; } } function populateVersions(index) { const select = qs("#drops-version"); if (!select) return; const previous = select.value || "v1"; select.innerHTML = ""; for (const table of index.tables || []) { const opt = document.createElement("option"); opt.value = table.version; opt.textContent = table.label; select.appendChild(opt); } if ([...select.options].some((opt) => opt.value === previous)) { select.value = previous; } } function populateFilters(rows) { fillSelect(qs("#drops-episode"), uniqueSorted(rows, "episode"), "All episodes"); fillSelect(qs("#drops-difficulty"), uniqueSorted(rows, "difficulty"), "All difficulties"); fillSelect(qs("#drops-section"), uniqueSorted(rows, "section_id"), "All Section IDs"); } function visibleRows() { const search = state.filters.search.trim().toLowerCase(); return state.rows.filter((row) => { if (state.filters.episode && row.episode !== state.filters.episode) return false; if (state.filters.difficulty && row.difficulty !== state.filters.difficulty) return false; if (state.filters.section && row.section_id !== state.filters.section) return false; if (search) { const haystack = [ row.mode, row.episode, row.difficulty, row.section_id, row.source, row.item, row.item_code, row.rate, ].join(" ").toLowerCase(); if (!haystack.includes(search)) return false; } return true; }); } function renderTable() { const box = qs("#drops-placeholder"); if (!box) return; const rows = visibleRows(); const tableLabel = state.table?.label || "Peeps"; const shown = rows.slice(0, 1000); const body = shown.map((row) => { const item = row.item || row.item_code || "—"; const itemCode = row.item_code || "—"; return ` ${esc(labelValue(row.mode))} ${esc(labelValue(row.episode))} ${esc(labelValue(row.difficulty))} ${esc(row.section_id || "—")} ${esc(labelValue(row.source || "—"))} ${esc(item)} ${esc(itemCode)} ${esc(row.rate || "—")} `; }).join(""); const truncation = rows.length > shown.length ? ` Showing first ${shown.length.toLocaleString()}.` : ""; box.innerHTML = `
Peeps ${esc(tableLabel)} drop table ${rows.length.toLocaleString()} matching rows.${truncation}
${state.rows.length.toLocaleString()} total rows
${body || ``}
Mode Episode Difficulty Section ID Source Item Item Code Rate
No drops match these filters.
`; } async function loadPeeps() { setStatus("Loading Peeps drop tables..."); if (!state.index) { state.index = await fetchJson("generated/drops/peeps/index.json"); populateVersions(state.index); } const version = qs("#drops-version")?.value || "v1"; const table = (state.index.tables || []).find((entry) => entry.version === version); if (!table) { setStatus("No drop table is configured for that version.", "error"); return; } state.table = table; state.rows = await fetchJson(`generated/drops/peeps/${table.path}`); state.filters.episode = ""; state.filters.difficulty = ""; state.filters.section = ""; state.filters.search = ""; if (qs("#drops-search")) qs("#drops-search").value = ""; populateFilters(state.rows); if (qs("#drops-episode")) qs("#drops-episode").value = ""; if (qs("#drops-difficulty")) qs("#drops-difficulty").value = ""; if (qs("#drops-section")) qs("#drops-section").value = ""; renderTable(); } async function updateMode() { const mode = qs("#drops-mode")?.value || "peeps"; const peepsControls = qs("#drops-peeps-controls"); if (mode === "hardcore") { if (peepsControls) peepsControls.hidden = true; setStatus("Hardcore drop tables coming next."); return; } if (peepsControls) peepsControls.hidden = false; try { await loadPeeps(); } catch (err) { setStatus(err?.message || "Unable to load drop table.", "error"); } } document.addEventListener("DOMContentLoaded", () => { qs("#drops-mode")?.addEventListener("change", updateMode); qs("#drops-version")?.addEventListener("change", loadPeeps); qs("#drops-episode")?.addEventListener("change", (event) => { state.filters.episode = event.target.value; renderTable(); }); qs("#drops-difficulty")?.addEventListener("change", (event) => { state.filters.difficulty = event.target.value; renderTable(); }); qs("#drops-section")?.addEventListener("change", (event) => { state.filters.section = event.target.value; renderTable(); }); qs("#drops-search")?.addEventListener("input", (event) => { state.filters.search = event.target.value; renderTable(); }); updateMode(); }); })();