diff --git a/app.js b/app.js
deleted file mode 100644
index c43f528..0000000
--- a/app.js
+++ /dev/null
@@ -1,1406 +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)
- ? (typeof data.detail === "string" ? data.detail : JSON.stringify(data.detail))
- : "request failed";
- throw new Error(message);
- }
-
- return data;
-}
-
-
-/* small helpers */
-
-function escapeHtml(value){
- return String(value == null ? "" : value)
- .replace(/&/g, "&")
- .replace(//g, ">")
- .replace(/"/g, """)
- .replace(/'/g, "'");
-}
-
-function toBackendTime(localValue){
- // datetime-local gives "YYYY-MM-DDTHH:MM" -> "YYYY-MM-DD HH:MM:00"
- if(!localValue){
- return null;
- }
- let value = localValue.replace("T", " ");
- if(value.length === 16){
- value += ":00";
- }
- return value;
-}
-
-
-// ---------- 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.classList.add("success");
- errorBox.classList.remove("error");
- errorBox.textContent = "account created, you can log in now";
-
- } catch(error){
- errorBox.classList.remove("success");
- errorBox.classList.add("error");
- errorBox.textContent = error.message;
- }
-}
-
-
-function logout(){
- clearToken();
- showLogin();
-}
-
-
-// ---------- shared state ----------
-
-
-let calendarWeekOffset = 0;
-let cachedDashboardEvents = [];
-let cachedRooms = [];
-let currentUserId = null;
-let currentUsername = null;
-
-
-async function ensureMe(){
- if(currentUserId !== null){
- return;
- }
- let me = await apiFetch("/me");
- currentUserId = me.id;
- currentUsername = me.username;
-}
-
-async function ensureRooms(force){
- if(cachedRooms.length && !force){
- return cachedRooms;
- }
- let roomsData = await apiFetch("/rooms");
- cachedRooms = roomsData.rooms || [];
- return cachedRooms;
-}
-
-
-// ---------- 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"){ loadSettings(); }
-}
-
-
-// ---------- dashboard ----------
-
-
-async function loadDashboard(){
-
- let content = document.getElementById("content");
-
- content.innerHTML = `
-
Dashboard
- Loading...
- `;
-
- calendarWeekOffset = 0;
-
- try{
- await ensureMe();
- let rooms = await ensureRooms(true);
-
- let eventsData = await apiFetch("/events");
- cachedDashboardEvents = eventsData.events || [];
-
- let roomsHtml =
- rooms.length
- ? rooms.map(room => `
- ${escapeHtml(room.room_name)}
- `).join("")
- : `You have no rooms yet.
`;
-
- content.innerHTML = `
- Dashboard
-
-
-
Welcome back, ${escapeHtml(currentUsername)}
-
Overview of your calendar.
-
-
-
- ${buildWeekCalendarHtml(cachedDashboardEvents, calendarWeekOffset)}
-
-
-
-
Your rooms
- ${roomsHtml}
-
-
-
-
-
- `;
-
- } catch(error){
- content.innerHTML = `
- Dashboard
- Could not load dashboard: ${escapeHtml(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";
- await ensureRooms(true);
- 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{
- await apiFetch("/join-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: inviteCode })
- });
-
- messageBox.textContent = "joined room";
- await ensureRooms(true);
- 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 status = (event.status || "").toLowerCase();
- let type = (event.type || "").toLowerCase();
-
- if(status === "proposed"){
- return "event-proposed";
- }
-
- if(type === "busy"){
- return "event-busy";
- }
-
- if(status === "confirmed"){
- return "event-confirmed";
- }
-
- 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(/[- :T]/);
- return new Date(
- parts[0], parts[1] - 1, parts[2],
- parts[3] || 0, parts[4] || 0
- );
- }
-
- 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}
- ${escapeHtml(p.event.title)}
-
- `;
- }).join("");
-
- let isToday =
- day.getFullYear() === today.getFullYear()
- && day.getMonth() === today.getMonth()
- && day.getDate() === today.getDate();
-
- return `
-
- `;
- }).join("");
-
- return `
-
-
-
- confirmed
- proposed
- busy
- private (others)
-
-
-
- `;
-}
-
-
-// ---------- rooms page ----------
-
-
-async function loadRooms(){
-
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Rooms
- Loading...
- `;
-
- try{
- await ensureMe();
- let rooms = await ensureRooms(true);
-
- let searchCard = `
-
-
Find a room by invite code
-
-
-
- `;
-
- if(rooms.length === 0){
- content.innerHTML = `
- Rooms
- ${searchCard}
- Your shared rooms appear here.
- `;
- return;
- }
-
- let roomsHtml =
- rooms.map(room => {
- let isOwner = room.owner_id === currentUserId;
- return `
-
-
-
${escapeHtml(room.room_name)}
- ${isOwner ? `owner` : ""}
-
-
invite code: ${escapeHtml(room.invite_code || "-")}
-
-
-
-
- ${isOwner
- ? ``
- : ``}
-
-
-
- `;
- }).join("");
-
- content.innerHTML = `
- Rooms
- ${searchCard}
- ${roomsHtml}
- `;
-
- } catch(error){
- content.innerHTML = `
- Rooms
- Could not load rooms: ${escapeHtml(error.message)}
- `;
- }
-}
-
-
-async function handleSearchRoom(event){
-
- event.preventDefault();
-
- let inviteCode = document.getElementById("search-room-code").value;
- let resultBox = document.getElementById("search-room-result");
-
- resultBox.textContent = "searching...";
-
- try{
- let data = await apiFetch("/search-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: inviteCode })
- });
-
- let room = data.room || data;
-
- if(!room || (!room.room_name && !room.id)){
- resultBox.textContent = "no room found for that code";
- return;
- }
-
- resultBox.innerHTML = `
-
-
${escapeHtml(room.room_name || "room")}
-
-
-
-
- `;
-
- } catch(error){
- resultBox.textContent = error.message;
- }
-}
-
-
-async function handleJoinRoomByCode(inviteCode){
- try{
- await apiFetch("/join-room", {
- method: "POST",
- body: JSON.stringify({ invite_code: inviteCode })
- });
- await ensureRooms(true);
- loadRooms();
- } catch(error){
- let resultBox = document.getElementById("search-room-result");
- if(resultBox){ resultBox.textContent = error.message; }
- }
-}
-
-
-async function showRoomMembers(roomId){
-
- let container = document.getElementById(`room-members-${roomId}`);
- if(!container){ return; }
-
- container.innerHTML = `loading members...`;
-
- try{
- let data = await apiFetch(`/rooms/${roomId}/members`);
- let members = data.members || [];
-
- if(members.length === 0){
- container.innerHTML = `no members`;
- return;
- }
-
- container.innerHTML =
- members.map(m => `
- ${escapeHtml(m.username || ("user " + m.id))}
- `).join("");
-
- } catch(error){
- container.innerHTML = `${escapeHtml(error.message)}`;
- }
-}
-
-
-async function handleLeaveRoom(roomId){
-
- let messageBox = document.getElementById(`room-message-${roomId}`);
-
- try{
- await apiFetch(`/rooms/${roomId}/members/me`, { method: "DELETE" });
- await ensureRooms(true);
- loadRooms();
- } catch(error){
- if(messageBox){ messageBox.textContent = error.message; }
- }
-}
-
-
-async function handleDeleteRoom(roomId){
-
- let messageBox = document.getElementById(`room-message-${roomId}`);
-
- try{
- await apiFetch(`/room/${roomId}`, { method: "DELETE" });
- await ensureRooms(true);
- loadRooms();
- } catch(error){
- if(messageBox){ messageBox.textContent = error.message; }
- }
-}
-
-
-async function loadRoomEvents(roomId){
-
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Room events
- Loading...
- `;
-
- try{
- await ensureMe();
- 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: ${escapeHtml(error.message)}
- `;
- }
-}
-
-
-// ---------- events page ----------
-
-
-// kind -> backend fields mapping
-const EVENT_KINDS = {
- confirmed: { type: "event", visibility: "public", status: "confirmed", label: "Confirmed event", desc: "A regular event everyone can see." },
- proposed: { type: "event", visibility: "public", status: "proposed", label: "Propose event", desc: "Suggest a time and collect responses." },
- busy: { type: "busy", visibility: "public", status: "confirmed", label: "Busy", desc: "Block time without sharing details." },
- private: { type: "private", visibility: "private", status: "confirmed", label: "Private", desc: "Only you can see the details." }
-};
-
-
-async function loadEvents(){
-
- let content = document.getElementById("content");
-
- content.innerHTML = `
- Events
- Loading...
- `;
-
- try{
- await ensureMe();
- let rooms = await ensureRooms();
-
- let data = await apiFetch("/events");
- let events = data.events || [];
-
- content.innerHTML = `
- Events
- ${renderCreateEventCard(rooms)}
- Upcoming events
- ${renderEventCards(events)}
- `;
-
- } catch(error){
- content.innerHTML = `
- Events
- Could not load events: ${escapeHtml(error.message)}
- `;
- }
-}
-
-
-function renderCreateEventCard(rooms){
-
- let roomOptions =
- rooms.length
- ? rooms.map(r => ``).join("")
- : ``;
-
- let kindOptions =
- Object.keys(EVENT_KINDS).map((key, index) => {
- let k = EVENT_KINDS[key];
- return `
-
- `;
- }).join("");
-
- return `
-
- `;
-}
-
-
-function onEventKindChange(){
-
- let selected =
- document.querySelector('input[name="event-kind"]:checked');
-
- let wrap = document.getElementById("event-minpeople-wrap");
-
- if(!selected || !wrap){ return; }
-
- // min people is only meaningful when proposing
- wrap.style.display = (selected.value === "proposed") ? "block" : "none";
-}
-
-
-async function handleCreateEvent(event){
-
- event.preventDefault();
-
- let messageBox = document.getElementById("create-event-message");
- messageBox.classList.remove("success", "error");
-
- let kindKey =
- (document.querySelector('input[name="event-kind"]:checked') || {}).value || "confirmed";
-
- let kind = EVENT_KINDS[kindKey];
-
- let roomId = document.getElementById("event-room").value;
- let title = document.getElementById("event-title").value;
- let description = document.getElementById("event-description").value;
- let start = toBackendTime(document.getElementById("event-start").value);
- let end = toBackendTime(document.getElementById("event-end").value);
-
- let minPeople = 0;
- if(kindKey === "proposed"){
- minPeople = parseInt(document.getElementById("event-minpeople").value, 10) || 0;
- }
-
- if(!roomId){
- messageBox.classList.add("error");
- messageBox.textContent = "please create or join a room first";
- return;
- }
-
- let payload = {
- room_id: parseInt(roomId, 10),
- type: kind.type,
- title: title,
- description: description,
- start_time: start,
- end_time: end,
- visibility: kind.visibility,
- status: kind.status,
- min_people: minPeople
- };
-
- try{
- await apiFetch("/events", {
- method: "POST",
- body: JSON.stringify(payload)
- });
-
- messageBox.classList.add("success");
- messageBox.textContent = `${kind.label} created`;
-
- // refresh dashboard cache and reload the events list
- loadEvents();
-
- } catch(error){
- messageBox.classList.add("error");
- messageBox.textContent = error.message;
- }
-}
-
-
-function eventBadge(event){
-
- let status = (event.status || "").toLowerCase();
- let type = (event.type || "").toLowerCase();
-
- if(event.visibility === "private"){
- return `private`;
- }
- if(status === "proposed"){
- return `proposed`;
- }
- if(type === "busy"){
- return `busy`;
- }
- return `confirmed`;
-}
-
-
-function renderEventCards(events){
-
- if(events.length === 0){
- return `No events scheduled.
`;
- }
-
- return events.map(event => {
-
- let isOwner = event.creator_id === currentUserId;
- let isProposed = (event.status || "").toLowerCase() === "proposed";
-
- return `
-
-
-
-
${escapeHtml(event.title)}
- ${eventBadge(event)}
-
-
-
${escapeHtml(event.description || "")}
-
-
${escapeHtml(formatEventTime(event.start_time, event.end_time))}
-
- ${isProposed && event.min_people
- ? `
needs at least ${event.min_people} people
`
- : ""}
-
-
-
-
-
-
- ${isOwner ? `` : ""}
- ${isOwner ? `` : ""}
-
-
-
-
-
-
-
- `;
- }).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" });
-
- let going = (result && result.going != null) ? result.going : "?";
- messageBox.textContent = `you responded: ${status} (${going} going)`;
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-
-async function viewResponses(eventId){
-
- let container = document.getElementById(`event-responses-${eventId}`);
- if(!container){ return; }
-
- container.innerHTML = `loading responses...
`;
-
- try{
- let data = await apiFetch(`/events/${eventId}/responses`);
- let responses = data.responses || [];
-
- if(responses.length === 0){
- container.innerHTML = `no responses yet
`;
- return;
- }
-
- container.innerHTML = `
-
- ${responses.map(r => `
-
- ${escapeHtml(r.username || ("user " + r.user_id))}: ${escapeHtml(r.status)}
-
- `).join("")}
-
- `;
-
- } catch(error){
- container.innerHTML = `${escapeHtml(error.message)}
`;
- }
-}
-
-
-async function toggleEditEvent(eventId){
-
- let container = document.getElementById(`event-edit-${eventId}`);
- if(!container){ return; }
-
- if(container.innerHTML.trim() !== ""){
- container.innerHTML = "";
- return;
- }
-
- try{
- let event = await apiFetch(`/events/${eventId}`);
- let ev = event.event || event;
-
- let startVal = (ev.start_time || "").replace(" ", "T").slice(0, 16);
- let endVal = (ev.end_time || "").replace(" ", "T").slice(0, 16);
-
- container.innerHTML = `
-
-
- `;
-
- } catch(error){
- container.innerHTML = `${escapeHtml(error.message)}
`;
- }
-}
-
-
-async function handleUpdateEvent(event, eventId){
-
- event.preventDefault();
-
- let messageBox = document.getElementById(`edit-message-${eventId}`);
-
- let payload = {
- title: document.getElementById(`edit-title-${eventId}`).value,
- description: document.getElementById(`edit-desc-${eventId}`).value,
- start_time: toBackendTime(document.getElementById(`edit-start-${eventId}`).value),
- end_time: toBackendTime(document.getElementById(`edit-end-${eventId}`).value),
- min_people: parseInt(document.getElementById(`edit-minpeople-${eventId}`).value, 10) || 0
- };
-
- try{
- await apiFetch(`/events/${eventId}`, {
- method: "PATCH",
- body: JSON.stringify(payload)
- });
-
- messageBox.classList.add("success");
- messageBox.textContent = "event updated";
- loadEvents();
-
- } catch(error){
- messageBox.textContent = error.message;
- }
-}
-
-
-async function handleDeleteEvent(eventId){
-
- try{
- await apiFetch(`/events/${eventId}`, { method: "DELETE" });
-
- let card = document.getElementById(`event-card-${eventId}`);
- if(card){ card.remove(); }
-
- } catch(error){
- let messageBox = document.getElementById(`event-response-${eventId}`);
- if(messageBox){ messageBox.textContent = error.message; }
- }
-}
-
-
-// ---------- settings page ----------
-
-
-const A11Y_DEFAULTS = {
- contrast: false,
- underline: false,
- reduceMotion: false,
- dyslexic: false,
- fontScale: "1"
-};
-
-
-function getA11ySettings(){
- try{
- let raw = localStorage.getItem("a11y");
- if(!raw){ return { ...A11Y_DEFAULTS }; }
- return { ...A11Y_DEFAULTS, ...JSON.parse(raw) };
- } catch(e){
- return { ...A11Y_DEFAULTS };
- }
-}
-
-
-function saveA11ySettings(settings){
- localStorage.setItem("a11y", JSON.stringify(settings));
-}
-
-
-function applyA11ySettings(){
-
- let s = getA11ySettings();
- let body = document.body;
-
- body.classList.toggle("a11y-contrast", !!s.contrast);
- body.classList.toggle("a11y-underline", !!s.underline);
- body.classList.toggle("a11y-reduce-motion", !!s.reduceMotion);
- body.classList.toggle("a11y-dyslexic", !!s.dyslexic);
-
- document.documentElement.style.setProperty("--font-scale", s.fontScale || "1");
-}
-
-
-function updateA11ySetting(key, value){
- let s = getA11ySettings();
- s[key] = value;
- saveA11ySettings(s);
- applyA11ySettings();
-}
-
-
-async function loadSettings(){
-
- let content = document.getElementById("content");
- let s = getA11ySettings();
-
- let isDark = document.body.classList.contains("dark");
-
- content.innerHTML = `
- Settings
-
-
-
Appearance
-
-
-
-
Dark mode
-
Switch between the light paper theme and the dark theme.
-
-
-
-
-
-
-
-
-
Text size
-
Scale all text for easier reading.
-
-
-
-
-
-
-
-
-
-
Accessibility
-
-
-
-
High contrast
-
Increase contrast and thicken borders for better visibility.
-
-
-
-
-
-
-
-
-
Underline links
-
Always underline links so they stand out from regular text.
-
-
-
-
-
-
-
-
-
Reduce motion
-
Turn off transitions and animations across the app.
-
-
-
-
-
-
-
-
-
Dyslexia-friendly reading
-
Use a more legible font with extra letter and line spacing.
-
-
-
-
-
-
-
-
-
-
-
-
-
Account
-
Loading account...
-
-
- `;
-
- // load account info via /me
- try{
- await ensureMe();
- let box = document.getElementById("settings-account");
- if(box){
- box.textContent = `Signed in as ${currentUsername} (id ${currentUserId})`;
- }
- } catch(e){ /* ignore */ }
-}
-
-
-function resetA11ySettings(){
- saveA11ySettings({ ...A11Y_DEFAULTS });
- applyA11ySettings();
- loadSettings();
-}
-
-
-// ---------- theme ----------
-
-
-function toggleTheme(){
-
- document.body.classList.toggle("dark");
-
- let theme =
- document.body.classList.contains("dark") ? "dark" : "light";
-
- localStorage.setItem("theme", theme);
-}
-
-
-// ---------- init ----------
-
-
-let savedTheme = localStorage.getItem("theme");
-
-if(savedTheme === "dark"){
- document.body.classList.add("dark");
-}
-
-applyA11ySettings();
-
-if(getToken()){
- showApp();
- showPage("dashboard");
-} else {
- showLogin();
-}