diff --git a/frontend/www/app.js b/frontend/www/app.js
deleted file mode 100644
index 0845cd6..0000000
--- a/frontend/www/app.js
+++ /dev/null
@@ -1,1267 +0,0 @@
-const API_BASE = "/api";
-
-function getToken(){
- return localStorage.getItem("token");
-}
-
-function setToken(token){
- localStorage.setItem("token", token);
-}
-
-function clearToken(){
- localStorage.removeItem("token");
-}
-
-async function apiFetch(path, options = {}){
- let headers = options.headers ? { ...options.headers } : {};
- headers["Content-Type"] = "application/json";
-
- let token = getToken();
- if(token){
- headers["Authorization"] = "Bearer " + token;
- }
-
- let response = await fetch(API_BASE + path, { ...options, headers });
-
- if(response.status === 401){
- clearToken();
- showLogin();
- throw new Error("not authenticated");
- }
-
- let data = await response.json().catch(() => null);
-
- if(!response.ok){
- let message = (data && data.detail) ? data.detail : "request failed";
- throw new Error(message);
- }
-
- return data;
-}
-
-// Small helper: several endpoints (join-room, search-room, create event,
-// delete room) return HTTP 200 with a { status: "failed" | "denied" | ... }
-// body instead of a real error status. apiFetch only throws on non-2xx,
-// so callers that care about these soft failures check result.status
-// themselves — see handleJoinRoom, handleSearchRoom, handleCreateEvent,
-// handleDeleteRoom below.
-
-// ---------- date helpers ----------
-
-// datetime-local inputs give "YYYY-MM-DDTHH:MM"; the backend stores
-// "YYYY-MM-DD HH:MM" (see the create_event() example in events.py).
-function toBackendDateTime(localValue){
- return localValue ? localValue.replace("T", " ") : localValue;
-}
-
-function toDatetimeLocal(value){
- if(!value) return "";
-
- let d = new Date(value);
- if(isNaN(d.getTime())) return "";
-
- let pad = n => String(n).padStart(2, "0");
-
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
-}
-
-// ---------- auth ----------
-
-function showLogin(){
- document.getElementById("app").style.display = "none";
- document.getElementById("login-screen").style.display = "flex";
-}
-
-function showApp(){
- document.getElementById("login-screen").style.display = "none";
- document.getElementById("app").style.display = "grid";
-}
-
-async function handleLogin(event){
- event.preventDefault();
-
- let username = document.getElementById("login-username").value;
- let password = document.getElementById("login-password").value;
- let errorBox = document.getElementById("login-error");
-
- errorBox.textContent = "";
-
- try{
- let result = await apiFetch("/login", {
- method: "POST",
- body: JSON.stringify({ username, password })
- });
-
- if(result.status !== "success"){
- errorBox.textContent = "invalid username or password";
- return;
- }
-
- setToken(result.access_token);
- showApp();
- showPage("dashboard");
-
- } catch(error){
- errorBox.textContent = error.message;
- }
-}
-
-async function handleRegister(event){
- event.preventDefault();
-
- let username = document.getElementById("login-username").value;
- let password = document.getElementById("login-password").value;
- let errorBox = document.getElementById("login-error");
-
- errorBox.textContent = "";
-
- try{
- await apiFetch("/create-user", {
- method: "POST",
- body: JSON.stringify({ username, password })
- });
-
- errorBox.style.color = "green";
- errorBox.textContent = "account created, you can log in now";
-
- } catch(error){
- errorBox.style.color = "";
- errorBox.textContent = error.message;
- }
-}
-
-function logout(){
- clearToken();
- showLogin();
-}
-
-// ---------- page router ----------
-
-function showPage(page){
- let content = document.getElementById("content");
-
- document.querySelectorAll("nav a").forEach(link => link.classList.remove("active"));
-
- let activeLink = document.querySelector(`nav a[data-page="${page}"]`);
- if(activeLink){
- activeLink.classList.add("active");
- }
-
- if(page === "dashboard") loadDashboard();
- if(page === "rooms") loadRooms();
- if(page === "events") loadEvents();
-
- if(page === "settings"){
- renderSettingsPage(content);
- }
-}
-
-// ---------- dashboard ----------
-
-let calendarWeekOffset = 0;
-let cachedDashboardEvents = [];
-let cachedRoomsForForm = [];
-let currentUserId = null;
-
-async function loadDashboard(){
- let content = document.getElementById("content");
-
- content.innerHTML = `
-
Dashboard
- Loading...
- `;
-
- calendarWeekOffset = 0;
-
- try{
- let me = await apiFetch("/me");
- currentUserId = me.id;
-
- let roomsData = await apiFetch("/rooms");
- let rooms = roomsData.rooms || [];
- cachedRoomsForForm = rooms;
-
- let eventsData = await apiFetch("/events");
- cachedDashboardEvents = eventsData.events || [];
-
- let roomsHtml = rooms.length
- ? rooms.map(room => `${room.name}
`).join("")
- : `You have no rooms yet.
`;
-
- content.innerHTML = `
- Dashboard
-
-
-
Welcome back, ${me.username}
-
Overview of your calendar.
-
-
-
-
-
-
-
- ${buildWeekCalendarHtml(cachedDashboardEvents, calendarWeekOffset)}
-
-
-
-
Your rooms
- ${roomsHtml}
-
-
-
-
-
- `;
-
- } catch(error){
- content.innerHTML = `
- Dashboard
- Could not load dashboard: ${error.message}
- `;
- }
-}
-
-async function handleCreateRoom(event){
- event.preventDefault();
-
- let roomName = document.getElementById("create-room-name").value;
- let inviteCode = document.getElementById("create-room-code").value;
- let messageBox = document.getElementById("create-room-message");
-
- try{
- await apiFetch("/create-room", {
- method: "POST",
- body: JSON.stringify({ room_name: roomName, invite_code: inviteCode })
- });
-
- messageBox.textContent = "room created";
- loadDashboard();
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-async function handleJoinRoom(event){
- event.preventDefault();
-
- let inviteCode = document.getElementById("join-room-code").value;
- let messageBox = document.getElementById("join-room-message");
-
- try{
- let result = await apiFetch("/join-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: inviteCode })
- });
-
- // /join-room returns 200 with status:"failed" for both an
- // unknown code AND an already-joined room - it can't be told apart
- // client-side, so the message stays deliberately generic.
- if(result.status === "failed"){
- messageBox.textContent = "invalid invite code, or you're already in that room";
- return;
- }
-
- messageBox.textContent = "joined room";
- loadDashboard();
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-// ---------- calendar (week view) ----------
-
-const CALENDAR_SLOT_HOURS = 2;
-const CALENDAR_SLOT_HEIGHT = 48;
-const CALENDAR_START_HOUR = 0;
-const CALENDAR_END_HOUR = 24;
-const PIXELS_PER_MINUTE = CALENDAR_SLOT_HEIGHT / (CALENDAR_SLOT_HOURS * 60);
-
-function isEventCancelled(event){
- let status = (event.status || "").toLowerCase();
- return status === "cancelled" || status === "canceled" || status === "declined";
-}
-
-function getEventColorClass(event, currentUserId){
- let isOwnEvent = event.creator_id === currentUserId;
- let isPrivateOther = event.visibility === "private" && !isOwnEvent;
-
- if(isPrivateOther) return "event-private";
-
- let statusLower = (event.status || "").toLowerCase();
-
- if(statusLower === "confirmed") return "event-confirmed";
- if(statusLower === "busy") return "event-busy";
-
- // pending proposals fall back to the same tentative color as busy
- return "event-busy";
-}
-
-function getWeekStart(offset){
- let today = new Date();
- let base = new Date(today.getFullYear(), today.getMonth(), today.getDate() + (offset * 7));
- let dayOfWeek = base.getDay();
- let mondayDiff = (dayOfWeek === 0) ? -6 : (1 - dayOfWeek);
-
- base.setDate(base.getDate() + mondayDiff);
- return base;
-}
-
-function changeCalendarWeek(delta){
- calendarWeekOffset += delta;
-
- let calendarCard = document.getElementById("calendar-card");
- if(calendarCard){
- calendarCard.innerHTML = buildWeekCalendarHtml(cachedDashboardEvents, calendarWeekOffset);
- }
-}
-
-function buildWeekCalendarHtml(events, offset){
- let weekStart = getWeekStart(offset);
- let today = new Date();
- let dayNames = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
- let days = [];
-
- for(let i = 0; i < 7; i++){
- let d = new Date(weekStart);
- d.setDate(weekStart.getDate() + i);
- days.push(d);
- }
-
- let rangeLabel =
- days[0].toLocaleDateString("default", { day: "numeric", month: "short" })
- + " - "
- + days[6].toLocaleDateString("default", { day: "numeric", month: "short", year: "numeric" });
-
- let totalMinutes = (CALENDAR_END_HOUR - CALENDAR_START_HOUR) * 60;
- let trackHeight = ((CALENDAR_END_HOUR - CALENDAR_START_HOUR) / CALENDAR_SLOT_HOURS) * CALENDAR_SLOT_HEIGHT;
-
- let hourRows = "";
- for(let h = CALENDAR_START_HOUR; h < CALENDAR_END_HOUR; h += CALENDAR_SLOT_HOURS){
- hourRows += `
-
- ${String(h).padStart(2, "0")}:00
-
- `;
- }
-
- let dayColumns = days.map(day => {
-
- let dayEvents = events.filter(event => {
- if(!event.start_time || isEventCancelled(event)) return false;
-
- let eventStart = new Date(event.start_time);
- return (
- eventStart.getFullYear() === day.getFullYear()
- && eventStart.getMonth() === day.getMonth()
- && eventStart.getDate() === day.getDate()
- );
- }).sort((a, b) => new Date(a.start_time) - new Date(b.start_time));
-
- let layerEnds = [];
-
- let placed = dayEvents.map(event => {
- function parseLocalDate(dateString){
- let parts = dateString.split(/[- :]/);
- return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4]);
- }
-
- let start = parseLocalDate(event.start_time);
- let end = event.end_time ? parseLocalDate(event.end_time) : new Date(start.getTime() + 30 * 60000);
-
- let startMinutes = start.getHours() * 60 + start.getMinutes();
- let endMinutes = Math.max(startMinutes + 15, end.getHours() * 60 + end.getMinutes());
-
- let layer = 0;
- while(layerEnds[layer] !== undefined && layerEnds[layer] > startMinutes){
- layer++;
- }
- layerEnds[layer] = endMinutes;
-
- return { event, startMinutes, endMinutes, layer };
- });
-
- let maxLayer = placed.reduce((m, p) => Math.max(m, p.layer), 0);
-
- let blocksHtml = placed.map(p => {
- let topPx = (p.startMinutes - CALENDAR_START_HOUR * 60) * PIXELS_PER_MINUTE;
- let heightPx = (p.endMinutes - p.startMinutes) * PIXELS_PER_MINUTE;
- let leftPct = p.layer * 14;
- let widthPct = 100 - (maxLayer * 14);
- let colorClass = getEventColorClass(p.event, currentUserId);
-
- let timeLabel = new Date(p.event.start_time).toLocaleTimeString("nl-NL", {
- hour: "2-digit",
- minute: "2-digit",
- hour12: false
- });
-
- return `
-
- ${timeLabel}
- ${p.event.title}
-
- `;
- }).join("");
-
- let isToday =
- day.getFullYear() === today.getFullYear()
- && day.getMonth() === today.getMonth()
- && day.getDate() === today.getDate();
-
- return `
-
- `;
- }).join("");
-
- return `
-
-
-
- confirmed
- busy / pending
- private (others)
-
-
-
- `;
-}
-
-// ---------- create / propose event ----------
-
-function openCreateEventModal(prefill = {}){
- if(cachedRoomsForForm.length === 0){
- alert("You need to be in a room before you can create an event.");
- return;
- }
-
- let kindValue = prefill.kind || "proposal";
-
- let roomOptions = cachedRoomsForForm.map(room => `
-
- `).join("");
-
- openModal(`
- New event
-
- `);
-
- onEventTypeChange();
-}
-
-function onEventTypeChange(){
- let kind = document.getElementById("event-type").value;
- let wrap = document.getElementById("event-min-people-wrap");
- wrap.style.display = (kind === "proposal") ? "block" : "none";
-}
-
-async function handleCreateEvent(event){
- event.preventDefault();
-
- let kind = document.getElementById("event-type").value;
- let roomId = parseInt(document.getElementById("event-room").value, 10);
- let title = document.getElementById("event-title").value;
- let description = document.getElementById("event-description").value;
- let startLocal = document.getElementById("event-start").value;
- let endLocal = document.getElementById("event-end").value;
- let messageBox = document.getElementById("create-event-message");
-
- let payload = {
- room_id: roomId,
- title,
- description,
- start_time: toBackendDateTime(startLocal),
- end_time: toBackendDateTime(endLocal)
- };
-
- // Field values follow the convention from the create_event() example
- // in events.py: type="proposal", visibility="room", status="proposed".
- // "busy" / "private" extend that same pattern for the two other kinds.
- if(kind === "proposal"){
- payload.type = "proposal";
- payload.status = "proposed";
- payload.visibility = "room";
- payload.min_people = parseInt(document.getElementById("event-min-people").value, 10) || 1;
- } else if(kind === "busy"){
- payload.type = "busy";
- payload.status = "busy";
- payload.visibility = "room";
- payload.min_people = 0;
- } else {
- payload.type = "private";
- payload.status = "confirmed";
- payload.visibility = "private";
- payload.min_people = 0;
- }
-
- try{
- let result = await apiFetch("/events", {
- method: "POST",
- body: JSON.stringify(payload)
- });
-
- // POST /events returns 200 with status:"failed" (not an exception)
- // when the room membership check fails.
- if(result.status === "failed"){
- messageBox.textContent =
- result.reason === "not_room_member"
- ? "you're not a member of that room"
- : (result.reason || "could not create event");
- return;
- }
-
- closeModal();
-
- let activeLink = document.querySelector("nav a.active");
- showPage(activeLink ? activeLink.dataset.page : "dashboard");
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-// ---------- modal infrastructure ----------
-
-function ensureModalRoot(){
- let modalRoot = document.getElementById("modal-root");
-
- if(!modalRoot){
- modalRoot = document.createElement("div");
- modalRoot.id = "modal-root";
- document.body.appendChild(modalRoot);
- }
-
- return modalRoot;
-}
-
-function openModal(innerHtml){
- let modalRoot = ensureModalRoot();
-
- modalRoot.innerHTML = `
-
- `;
-
- document.addEventListener("keydown", handleModalEscape);
-}
-
-function closeModal(){
- let modalRoot = document.getElementById("modal-root");
- if(modalRoot) modalRoot.innerHTML = "";
-
- document.removeEventListener("keydown", handleModalEscape);
-}
-
-function handleModalEscape(event){
- if(event.key === "Escape") closeModal();
-}
-
-// ---------- rooms page ----------
-
-async function loadRooms(){
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Rooms
- Loading...
- `;
-
- try{
- let roomsData = await apiFetch("/rooms");
- let rooms = roomsData.rooms || [];
- cachedRoomsForForm = rooms;
-
- let actionsHtml = `
-
-
-
-
-
- `;
-
- if(rooms.length === 0){
- content.innerHTML = `
- Rooms
- ${actionsHtml}
- Your shared rooms appear here.
- `;
- return;
- }
-
- let roomsHtml = rooms.map(room => `
-
-
${room.name}
-
invite code: ${room.invite_code || "-"}
-
-
-
-
-
-
- `).join("");
-
- content.innerHTML = `
- Rooms
- ${actionsHtml}
- ${roomsHtml}
- `;
-
- } catch(error){
- content.innerHTML = `
- Rooms
- Could not load rooms: ${error.message}
- `;
- }
-}
-
-async function handleSearchRoom(event){
- event.preventDefault();
-
- let code = document.getElementById("search-room-code").value;
- let resultBox = document.getElementById("search-room-result");
-
- resultBox.innerHTML = `Searching...
`;
-
- try{
- let result = await apiFetch("/search-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: code })
- });
-
- if(result.status === "failed"){
- resultBox.innerHTML = `No room found for that code.
`;
- return;
- }
-
- resultBox.innerHTML = `
-
-
${result.room_name}
-
-
-
-
- `;
-
- } catch(error){
- resultBox.innerHTML = `${error.message}
`;
- }
-}
-
-async function handleJoinRoomByCode(code){
- try{
- let result = await apiFetch("/join-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: code })
- });
-
- if(result.status === "failed"){
- alert("Could not join: invalid code, or you're already a member.");
- return;
- }
-
- showPage("rooms");
-
- } catch(error){
- alert(error.message);
- }
-}
-
-async function loadRoomEvents(roomId){
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Room events
- Loading...
- `;
-
- try{
- let data = await apiFetch(`/rooms/${roomId}/events`);
- let events = data.events || [];
-
- content.innerHTML = `
- Room events
-
-
-
- ${renderEventCards(events)}
- `;
-
- } catch(error){
- content.innerHTML = `
- Room events
- Could not load events: ${error.message}
- `;
- }
-}
-
-// ---------- room detail (manage) ----------
-
-async function loadRoomDetail(roomId){
- let content = document.getElementById("content");
-
- content.innerHTML = `Room
Loading...
`;
-
- try{
- let data = await apiFetch(`/rooms/${roomId}`);
- let room = data.room;
- let members = data.members || [];
- let events = data.events || [];
-
- let ownerUsername = `user #${room.owner_id}`;
- try{
- let owner = await apiFetch(`/users/${room.owner_id}`);
- ownerUsername = owner.username;
- } catch(error){
- // keep the fallback label
- }
-
- content.innerHTML = `
- ${room.name}
-
-
-
Owner: ${ownerUsername}
-
Your role: ${room.role}
-
-
-
-
-
-
-
-
-
${renderMembersList(members)}
-
-
-
-
Leave or delete
-
-
-
-
-
- `;
-
- } catch(error){
- content.innerHTML = `Room
Could not load room: ${error.message}
`;
- }
-}
-
-function renderMembersList(members){
- if(!members.length){
- return `No members found.
`;
- }
-
- return members.map(member => `
-
- ${member.username}${member.role ? ` (${member.role})` : ""}
-
- `).join("");
-}
-
-async function refreshRoomMembers(roomId){
- let listBox = document.getElementById("room-members-list");
- if(!listBox) return;
-
- listBox.innerHTML = `Refreshing...
`;
-
- try{
- let data = await apiFetch(`/rooms/${roomId}/members`);
- listBox.innerHTML = renderMembersList(data.members || []);
- } catch(error){
- listBox.innerHTML = `Could not refresh members: ${error.message}
`;
- }
-}
-
-async function handleLeaveRoom(roomId){
- if(!confirm("Leave this room?")) return;
-
- try{
- await apiFetch(`/rooms/${roomId}/members/me`, { method: "DELETE" });
- showPage("rooms");
- } catch(error){
- alert("Could not leave room: " + error.message);
- }
-}
-
-async function handleDeleteRoom(roomId){
- if(!confirm("Delete this room? This cannot be undone.")) return;
-
- try{
- let result = await apiFetch(`/room/${roomId}`, { method: "DELETE" });
-
- if(result.status === "denied"){
- alert("You don't have permission to delete this room.");
- return;
- }
-
- if(result.status !== "deleted"){
- // the endpoint currently always returns this until it's implemented
- alert("Room was not deleted: " + (result.reason || "unknown reason"));
- return;
- }
-
- showPage("rooms");
-
- } catch(error){
- alert("Could not delete room: " + error.message);
- }
-}
-
-// ---------- events page ----------
-
-async function loadEvents(){
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Events
- Loading...
- `;
-
- try{
- let data = await apiFetch("/events");
- let events = data.events || [];
-
- content.innerHTML = `
- Events
-
-
-
- ${renderEventCards(events)}
- `;
-
- } catch(error){
- content.innerHTML = `
- Events
- Could not load events: ${error.message}
- `;
- }
-}
-
-function renderEventCards(events){
- if(events.length === 0){
- return `No events scheduled.
`;
- }
-
- return events.map(event => `
-
-
${event.title}
-
${event.description || ""}
-
${formatEventTime(event.start_time, event.end_time)}
-
status: ${event.status}
-
-
-
-
-
-
-
-
-
-
- `).join("");
-}
-
-function formatEventTime(start, end){
- if(!start) return "time unknown";
-
- let startDate = new Date(start);
- let endDate = end ? new Date(end) : null;
- let startText = startDate.toLocaleString();
-
- if(!endDate) return startText;
-
- return startText + " - " + endDate.toLocaleString();
-}
-
-async function handleRespond(eventId, status){
- let messageBox = document.getElementById(`event-response-${eventId}`);
-
- try{
- let result = await apiFetch(`/events/${eventId}/respond?status=${status}`, { method: "POST" });
- messageBox.textContent = `you responded: ${status} (${result.going} going)`;
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-// ---------- event detail / edit / delete ----------
-
-async function ensureCurrentUser(){
- if(currentUserId !== null) return currentUserId;
-
- try{
- let me = await apiFetch("/me");
- currentUserId = me.id;
- } catch(error){
- // isOwn checks just stay false
- }
-
- return currentUserId;
-}
-
-async function openEventDetailModal(eventId){
- openModal(`Loading...
`);
-
- try{
- await ensureCurrentUser();
-
- let eventDetail = await apiFetch(`/events/${eventId}`);
- let isOwn = eventDetail.creator_id === currentUserId;
-
- openModal(`
- ${eventDetail.title}
- ${formatEventTime(eventDetail.start_time, eventDetail.end_time)}
- ${eventDetail.description || ""}
- status: ${eventDetail.status}
-
- Responses
-
-
-
- ${isOwn ? `
-
-
- ` : ""}
-
-
- `);
-
- try{
- let responsesData = await apiFetch(`/events/${eventId}/responses`);
- let responses = responsesData.responses || [];
- let listBox = document.getElementById("event-responses-list");
-
- if(listBox){
- listBox.innerHTML = responses.length
- ? responses.map(r => `${r.username} — ${r.status}
`).join("")
- : `No responses yet.
`;
- }
- } catch(error){
- let listBox = document.getElementById("event-responses-list");
- if(listBox) listBox.innerHTML = `Could not load responses: ${error.message}
`;
- }
-
- } catch(error){
- openModal(`Could not load event: ${error.message}
`);
- }
-}
-
-async function openEditEventModal(eventId){
- try{
- let eventDetail = await apiFetch(`/events/${eventId}`);
-
- openModal(`
- Edit event
-
- `);
-
- } catch(error){
- alert("Could not load event: " + error.message);
- }
-}
-
-async function handleUpdateEvent(event, eventId){
- event.preventDefault();
-
- let messageBox = document.getElementById("edit-event-message");
-
- let payload = {
- title: document.getElementById("edit-event-title").value,
- description: document.getElementById("edit-event-description").value,
- start_time: toBackendDateTime(document.getElementById("edit-event-start").value),
- end_time: toBackendDateTime(document.getElementById("edit-event-end").value),
- min_people: parseInt(document.getElementById("edit-event-min-people").value, 10) || 1
- };
-
- try{
- await apiFetch(`/events/${eventId}`, {
- method: "PATCH",
- body: JSON.stringify(payload)
- });
-
- closeModal();
- showPage("events");
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-async function handleDeleteEvent(eventId){
- if(!confirm("Delete this event?")) return;
-
- try{
- await apiFetch(`/events/${eventId}`, { method: "DELETE" });
- closeModal();
- showPage("events");
- } catch(error){
- alert("Could not delete event: " + error.message);
- }
-}
-
-const A11Y_DEFAULTS = {
- reducedMotion: false,
- highContrast: false,
- fontScale: "normal",
- underlineLinks: false,
- alwaysShowFocus: false
-};
-
-function getA11ySettings(){
- try{
- let stored = JSON.parse(localStorage.getItem("a11y-settings"));
- return { ...A11Y_DEFAULTS, ...(stored || {}) };
- } catch(error){
- return { ...A11Y_DEFAULTS };
- }
-}
-
-function applyA11ySettings(settings){
- document.body.classList.toggle("reduced-motion", settings.reducedMotion);
- document.body.classList.toggle("high-contrast", settings.highContrast);
- document.body.classList.toggle("underline-links", settings.underlineLinks);
- document.body.classList.toggle("always-focus", settings.alwaysShowFocus);
-
- document.body.classList.remove("font-scale-large", "font-scale-xlarge");
- if(settings.fontScale === "large") document.body.classList.add("font-scale-large");
- if(settings.fontScale === "xlarge") document.body.classList.add("font-scale-xlarge");
-}
-
-function updateA11ySetting(key, value){
- let settings = getA11ySettings();
- settings[key] = value;
-
- localStorage.setItem("a11y-settings", JSON.stringify(settings));
- applyA11ySettings(settings);
-}
-
-function renderSettingsPage(content){
- let settings = getA11ySettings();
-
- content.innerHTML = `
- Settings
-
-
-
Account
-
-
-
-
-
Accessibility
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Appearance
-
-
-
-
-
Diagnostics
-
-
-
-
-
- `;
-}
-
-async function runDiagnostics(){
- let resultBox = document.getElementById("diagnostics-result");
- resultBox.textContent = "Checking...";
-
- try{
- let home = await apiFetch("/");
- let db = await apiFetch("/database");
- resultBox.textContent = `${home.service}: ${home.status}. Tables: ${(db.tables || []).join(", ")}`;
- } catch(error){
- resultBox.textContent = "Diagnostics failed: " + error.message;
- }
-}
-
-async function loadAsciiArt(){
- let box = document.getElementById("ascii-art-box");
-
- try{
- let headers = {};
- let token = getToken();
- if(token) headers["Authorization"] = "Bearer " + token;
-
- let response = await fetch(API_BASE + "/ascii-art", { headers });
- let text = await response.text();
-
- box.textContent = text;
- box.hidden = false;
-
- } catch(error){
- box.textContent = "Could not load ascii art.";
- box.hidden = false;
- }
-}
-
-function toggleTheme(){
- document.body.classList.toggle("dark");
-
- let theme = document.body.classList.contains("dark") ? "dark" : "light";
- localStorage.setItem("theme", theme);
-}
-
-let savedTheme = localStorage.getItem("theme");
-if(savedTheme === "dark"){
- document.body.classList.add("dark");
-}
-
-applyA11ySettings(getA11ySettings());
-
-if(getToken()){
- showApp();
- showPage("dashboard");
-} else {
- showLogin();
-}
\ No newline at end of file