diff --git a/index.html b/index.html
index 58fc333..7905ff1 100644
--- a/index.html
+++ b/index.html
@@ -38,7 +38,7 @@
—
-
Combined W–L–T
+
Team Win %
—
@@ -71,10 +71,10 @@
| # |
- Player |
- ITC Points |
- W–L–T |
- ITC Rank |
+ Player |
+ ITC Points |
+ Win % |
+ ITC Rank |
diff --git a/src/main.js b/src/main.js
index f7a9229..838a9d1 100644
--- a/src/main.js
+++ b/src/main.js
@@ -57,8 +57,57 @@ const CACHE_KEY = "gg_board_v3";
const $ = (id) => document.getElementById(id);
const fmtPts = (n) => Number(n || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 });
+const fmtWpct = (w, l, t) => { const g = (+w||0)+(+l||0)+(+t||0); return g ? ((+w||0)/g*100).toFixed(1)+'%' : '—'; };
const fullName = (u = {}) => [u.firstName, u.lastName].filter(Boolean).join(" ").trim();
+/* ---- Sort state ---- */
+let sortState = { col: 'pts', dir: 'desc' };
+let lastState = null;
+
+const sortCols = {
+ pts: { key: r => +(r.ITCPoints || 0), defaultDir: 'desc' },
+ name: { key: r => fullName(r.user || {}).toLowerCase() || '', defaultDir: 'asc' },
+ wpct: { key: r => { const g=(+r.wins||0)+(+r.losses||0)+(+r.ties||0); return g?(+r.wins||0)/g:-1; }, defaultDir: 'desc' },
+ rank: { key: r => +(r.placing || Infinity), defaultDir: 'asc' },
+};
+
+function applySort(rows) {
+ const { col, dir } = sortState;
+ const sc = sortCols[col] || sortCols.pts;
+ return [...rows].sort((a, b) => {
+ const av = sc.key(a), bv = sc.key(b);
+ if (typeof av === 'string') return dir === 'asc' ? av.localeCompare(bv) : bv.localeCompare(av);
+ return dir === 'asc' ? av - bv : bv - av;
+ });
+}
+
+function updateSortHeaders() {
+ document.querySelectorAll('th[data-sort]').forEach(th => {
+ const arrow = th.querySelector('.sort-arrow');
+ if (arrow) arrow.textContent = th.dataset.sort === sortState.col ? (sortState.dir === 'asc' ? ' ▲' : ' ▼') : '';
+ });
+}
+
+function initSortHeaders() {
+ document.querySelectorAll('th[data-sort]').forEach(th => {
+ const arrow = document.createElement('span');
+ arrow.className = 'sort-arrow text-xs opacity-60';
+ th.appendChild(arrow);
+ th.addEventListener('click', () => {
+ const col = th.dataset.sort;
+ if (sortState.col === col) {
+ sortState.dir = sortState.dir === 'asc' ? 'desc' : 'asc';
+ } else {
+ sortState.col = col;
+ sortState.dir = (sortCols[col] || sortCols.pts).defaultDir;
+ }
+ updateSortHeaders();
+ if (lastState) render(lastState);
+ });
+ });
+ updateSortHeaders();
+}
+
/* ---- Roster ---- */
async function getMemberIds(force) {
try {
@@ -122,6 +171,7 @@ function build(rows, ids) {
/* ---- Render ---- */
function render(state) {
+ lastState = state;
const { kept, rosterSize, updated, live, fromCache } = state;
$("dot").className = "w-2 h-2 rounded-full bg-success animate-pulse transition-colors";
@@ -147,7 +197,7 @@ function render(state) {
const top = kept[0], tu = top.user || {};
champ.classList.remove("hidden");
$("champ-name").textContent = fullName(tu) || tu.nickname || "—";
- $("champ-sub").textContent = `${top.wins || 0}–${top.losses || 0}–${top.ties || 0} · ITC #${top.placing ?? "—"}`;
+ $("champ-sub").textContent = `${fmtWpct(top.wins, top.losses, top.ties)} Win Rate · ITC #${top.placing ?? "—"}`;
$("champ-pts").textContent = fmtPts(top.ITCPoints);
// Stat strip
@@ -157,11 +207,11 @@ function render(state) {
best = Math.min(...kept.map(r => +r.placing || Infinity));
$("s-placed").textContent = kept.length;
$("s-top").textContent = fmtPts(top.ITCPoints);
- $("s-rec").textContent = `${W}–${L}–${T}`;
+ $("s-rec").textContent = fmtWpct(W, L, T);
$("s-best").textContent = isFinite(best) ? ("#" + best) : "—";
// Rows
- kept.forEach((r, i) => {
+ applySort(kept).forEach((r, i) => {
const u = r.user || {}, name = fullName(u) || u.nickname || "Unknown";
const tr = document.createElement("tr");
tr.innerHTML =
@@ -169,8 +219,7 @@ function render(state) {
`${esc(name)}` +
`${u.nickname && fullName(u) ? `"${esc(u.nickname)}"` : ""} | ` +
`${fmtPts(r.ITCPoints)} | ` +
- `` +
- `${r.wins || 0}–${r.losses || 0}–${r.ties || 0} | ` +
+ `${fmtWpct(r.wins, r.losses, r.ties)} | ` +
`#${r.placing ?? "—"} | `;
tb.appendChild(tr);
});
@@ -270,5 +319,6 @@ const SAMPLE = (() => {
/* ---- Go ---- */
$("refresh").addEventListener("click", () => load(true));
+initSortHeaders();
paintCache();
load(false);