feat(theme+comments): runtime colour theme and COMMENTS_ENABLED switch

Colour theme is configurable at runtime from two seed colours (brand + accent);
neutrals stay fixed for contrast safety. Tailwind v4 var()-based tokens let a
:root:root override recolour everything with no rebuild; the 50->950 ramps are
derived via a color-mix ladder. Config lives in the DB config table (admin UI:
Config > Farbschema, presets + custom pickers + live preview), served on the public
/event endpoint with env defaults (THEME_PRESET/PRIMARY/ACCENT), propagated live via
event-updated SSE, and cached in localStorage for a no-flash boot. The keepsake export
mirrors the same ladder in Rust so offline archives match the event theme.

COMMENTS_ENABLED (env, default true) is a boot-time kill-switch: the backend rejects
new comments with 403 and the frontend hides the comment button (feed card) and
panel/composer (lightbox). Existing comments stay in the DB, hidden, and return when
re-enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-19 15:21:55 +02:00
parent 6e0a760271
commit 57a907eca5
12 changed files with 765 additions and 99 deletions

View File

@@ -16,6 +16,7 @@
import { api } from '$lib/api';
import type { MeContextDto } from '$lib/types';
import { eventState, markClosed, markOpened, refreshEventState } from '$lib/event-state-store';
import { loadEventConfig } from '$lib/event-config-store';
let { children } = $props();
@@ -37,6 +38,10 @@
// Hooks up the appliedTheme → <html class="dark"> sync. Must run early so the
// first paint after hydration matches the saved preference.
initTheme();
// Load the public event config (colour theme + comments flag) and apply the
// palette. Applies for authed and pre-auth (/join) alike; the app.html boot script
// already painted the cached palette, this reconciles it with the server.
void loadEventConfig();
// Hydrate cross-cutting stores once on boot if the user is already authenticated.
// Page-level mounts will refresh again as needed.
if (getToken()) {
@@ -80,7 +85,10 @@
markClosed();
void refreshEventState();
}),
onSseEvent('event-opened', () => markOpened())
onSseEvent('event-opened', () => markOpened()),
// A host changed the theme (or privacy note) — re-fetch the public config so the
// palette updates live on every open client, not just after a reload.
onSseEvent('event-updated', () => void loadEventConfig())
);
});