/* Token kept in memory + sessionStorage so a refresh keeps you logged in
but closing the tab clears it. */
let token = sessionStorage.getItem("spectre_token") || null;
let me = sessionStorage.getItem("spectre_user") || null;
let ghostCache = [];
const $ = (id) => document.getElementById(id);
const loginView = $("login-view");
const dashView = $("dash-view");
/* ---------- API helper ---------- */
async function api(method, path, body, isForm = false) {
const headers = {};
if (token) headers.Authorization = "Bearer " + token;
let payload;
if (isForm) {
payload = body; // FormData
} else if (body) {
headers["Content-Type"] = "application/json";
payload = JSON.stringify(body);
}
const res = await fetch("/api" + path, { method, headers, body: payload });
if (res.status === 401) { doLogout(); throw new Error("Session expired"); }
const data = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(data.error || "Request failed");
return data;
}
/* ---------- Auth ---------- */
$("login-btn").addEventListener("click", async () => {
$("login-error").textContent = "";
try {
const data = await api("POST", "/login", {
username: $("username").value.trim(),
password: $("password").value,
});
token = data.token; me = data.username;
sessionStorage.setItem("spectre_token", token);
sessionStorage.setItem("spectre_user", me);
enterDash();
} catch (e) {
$("login-error").textContent = e.message;
}
});
$("password").addEventListener("keydown", (e) => { if (e.key === "Enter") $("login-btn").click(); });
$("logout-btn").addEventListener("click", doLogout);
function doLogout() {
token = null; me = null;
sessionStorage.removeItem("spectre_token");
sessionStorage.removeItem("spectre_user");
dashView.classList.remove("active");
loginView.classList.add("active");
}
function enterDash() {
loginView.classList.remove("active");
dashView.classList.add("active");
$("whoami").textContent = "@" + me;
loadGhosts();
loadSets();
}
/* ---------- Tabs ---------- */
document.querySelectorAll(".tab").forEach((t) =>
t.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach((x) => x.classList.remove("active"));
document.querySelectorAll(".tab-panel").forEach((x) => x.classList.remove("active"));
t.classList.add("active");
$("tab-" + t.dataset.tab).classList.add("active");
})
);
/* ---------- Ghosts ---------- */
async function loadGhosts() {
ghostCache = await api("GET", "/ghosts");
const grid = $("ghost-grid");
grid.innerHTML = "";
for (const g of ghostCache) {
const card = document.createElement("div");
card.className = "ghost-card" + (g.enabled ? "" : " disabled");
card.innerHTML = `