diff --git a/site/drop-tables.js b/site/drop-tables.js
index 312da66..282398f 100644
--- a/site/drop-tables.js
+++ b/site/drop-tables.js
@@ -14,6 +14,12 @@
section: "",
search: "",
},
+ sort: {
+ key: "source",
+ dir: "asc",
+ },
+ page: 1,
+ pageSize: 100,
};
const RARE_MODIFIER_VERSIONS = new Set(["v2", "bb"]);
@@ -184,6 +190,89 @@
fillSelect(qs("#drops-section"), uniqueSorted(rows, "section_id"), "All Section IDs");
}
+ const SORT_COLUMNS = [
+ ["mode", "Mode"],
+ ["episode", "Episode"],
+ ["difficulty", "Difficulty"],
+ ["section_id", "SECID"],
+ ["source", "Source"],
+ ["item", "Item"],
+ ["item_code", "Code"],
+ ["rate", "Rate"],
+ ];
+
+ const DIFFICULTY_SORT_ORDER = {
+ Normal: 0,
+ Hard: 1,
+ VeryHard: 2,
+ Ultimate: 3,
+ };
+
+ function rateSortValue(rate) {
+ const text = String(rate || "").replaceAll(",", "");
+ const match = text.match(/^(\d+(?:\.\d+)?)\/(\d+(?:\.\d+)?)$/);
+ if (!match) return Number.NEGATIVE_INFINITY;
+
+ const num = Number(match[1]);
+ const den = Number(match[2]);
+
+ if (!Number.isFinite(num) || !Number.isFinite(den) || den <= 0) {
+ return Number.NEGATIVE_INFINITY;
+ }
+
+ // Sort by actual probability, same as percentage conversion.
+ // 5/8 => 0.625, 1/8192 => 0.000122...
+ return num / den;
+ }
+
+ function sortValue(row, key) {
+ if (key === "difficulty") {
+ return DIFFICULTY_SORT_ORDER[row.difficulty] ?? 999;
+ }
+
+ if (key === "rate") {
+ return rateSortValue(adjustedRate(row.rate));
+ }
+
+ if (key === "item") {
+ return row.item || row.item_code || "";
+ }
+
+ return row[key] || "";
+ }
+
+ function sortedRows(rows) {
+ const { key, dir } = state.sort;
+ const factor = dir === "desc" ? -1 : 1;
+
+ return [...rows].sort((a, b) => {
+ const av = sortValue(a, key);
+ const bv = sortValue(b, key);
+
+ if (typeof av === "number" && typeof bv === "number") {
+ return (av - bv) * factor;
+ }
+
+ return String(av).localeCompare(String(bv), undefined, {
+ numeric: true,
+ sensitivity: "base",
+ }) * factor;
+ });
+ }
+
+ function sortHeader(key, label) {
+ const active = state.sort.key === key;
+ const arrow = active ? (state.sort.dir === "asc" ? "▲" : "▼") : "";
+ const ariaSort = active ? (state.sort.dir === "asc" ? "ascending" : "descending") : "none";
+
+ return `
+
+ | `;
+ }
+
function visibleRows() {
const search = state.filters.search.trim().toLowerCase();
@@ -216,9 +305,14 @@
const box = qs("#drops-placeholder");
if (!box) return;
- const rows = visibleRows();
+ const rows = sortedRows(visibleRows());
const tableLabel = state.table?.label || "Peeps";
- const shown = rows.slice(0, 1000);
+ const totalPages = Math.max(1, Math.ceil(rows.length / state.pageSize));
+ state.page = Math.min(Math.max(1, state.page), totalPages);
+
+ const start = (state.page - 1) * state.pageSize;
+ const shown = rows.slice(start, start + state.pageSize);
+ const end = start + shown.length;
const body = shown.map((row) => {
const item = row.item || row.item_code || "—";
@@ -238,8 +332,8 @@
`;
}).join("");
- const truncation = rows.length > shown.length
- ? ` Showing first ${shown.length.toLocaleString()}.`
+ const rangeText = rows.length
+ ? ` Showing ${Number(start + 1).toLocaleString()}-${Number(end).toLocaleString()}.`
: "";
const modifier = currentRareModifier();
const modifierNote = rareModifierEnabled() && modifier.pct > 0
@@ -250,7 +344,7 @@
Peeps ${esc(tableLabel)} drop table
- ${rows.length.toLocaleString()} matching rows.${truncation}
+ ${rows.length.toLocaleString()} matching rows.${rangeText}
${modifierNote}
${state.rows.length.toLocaleString()} total rows
@@ -259,19 +353,17 @@
- | Mode |
- Episode |
- Difficulty |
- Section ID |
- Source |
- Item |
- Item Code |
- Rate |
+ ${SORT_COLUMNS.map(([key, label]) => sortHeader(key, label)).join("")}
${body || `| No drops match these filters. |
`}
+
`;
}
@@ -299,6 +391,7 @@
state.filters.difficulty = "";
state.filters.section = "";
state.filters.search = "";
+ state.page = 1;
if (qs("#drops-search")) qs("#drops-search").value = "";
@@ -335,30 +428,61 @@
document.addEventListener("DOMContentLoaded", () => {
qs("#drops-mode")?.addEventListener("change", updateMode);
qs("#drops-version")?.addEventListener("change", loadPeeps);
- qs("#drops-rare-modifier")?.addEventListener("change", renderTable);
+ qs("#drops-rare-modifier")?.addEventListener("change", () => {
+ state.page = 1;
+ state.page = 1;
+ renderTable();
+ });
qs("#drops-rare-mode")?.addEventListener("change", (event) => {
state.filters.mode = event.target.value;
+ state.page = 1;
renderTable();
});
qs("#drops-episode")?.addEventListener("change", (event) => {
state.filters.episode = event.target.value;
+ state.page = 1;
renderTable();
});
qs("#drops-difficulty")?.addEventListener("change", (event) => {
state.filters.difficulty = event.target.value;
+ state.page = 1;
renderTable();
});
qs("#drops-section")?.addEventListener("change", (event) => {
state.filters.section = event.target.value;
+ state.page = 1;
renderTable();
});
qs("#drops-search")?.addEventListener("input", (event) => {
state.filters.search = event.target.value;
+ state.page = 1;
+ renderTable();
+ });
+
+ qs("#drops-placeholder")?.addEventListener("click", (event) => {
+ const pageButton = event.target.closest("[data-drops-page]");
+ if (pageButton) {
+ state.page += pageButton.dataset.dropsPage === "next" ? 1 : -1;
+ renderTable();
+ return;
+ }
+
+ const button = event.target.closest("[data-drops-sort]");
+ if (!button) return;
+
+ const key = button.dataset.dropsSort;
+ if (state.sort.key === key) {
+ state.sort.dir = state.sort.dir === "asc" ? "desc" : "asc";
+ } else {
+ state.sort.key = key;
+ state.sort.dir = "asc";
+ }
+
renderTable();
});
diff --git a/site/drops.html b/site/drops.html
index 9bec22d..1a7886f 100644
--- a/site/drops.html
+++ b/site/drops.html
@@ -126,6 +126,6 @@
-
+