Index: src/assets/js/ward-theme.js
===================================================================
--- src/assets/js/ward-theme.js	(revision 39dab6b0c3b5fd70bb2f06743430f0014384b3b7)
+++ src/assets/js/ward-theme.js	(revision 39dab6b0c3b5fd70bb2f06743430f0014384b3b7)
@@ -0,0 +1,63 @@
+/**
+ * De themakleur (SkyPalette): één gekozen kleur die de hele ward kleurt.
+ *
+ * De acht presets en de luminantie-regel komen LETTERLIJK uit Shaer (iOS is de
+ * bron): dezelfde kleuren, en dezelfde beslissing welke tekst erop leesbaar is
+ * (donker op licht, wit op de rest). Een handgehouden lijstje zou uit de pas
+ * gaan lopen; luminantie beslist.
+ *
+ * Opgeslagen in localStorage, per apparaat, net als in de app.
+ */
+'use strict';
+
+export const PRESETS = [
+  0x5A32E6, // paars, de vertrouwde
+  0x1E6AE1, // oceaan
+  0x0E9B8A, // zeegroen
+  0x2E7D32, // bos
+  0xF59E0B, // zon
+  0xFF6B4A, // koraal
+  0xE255A1, // roze
+  0x1F2937, // nacht
+];
+const FALLBACK = 0x5A32E6;
+const KEY = 'ward.skyColor';
+
+const hex = (rgb) => '#' + rgb.toString(16).padStart(6, '0');
+
+/** Wit of donker, wat leesbaar is op deze kleur. Zelfde som als in de app. */
+export function onSky(rgb) {
+  const r = ((rgb >> 16) & 0xff) / 255;
+  const g = ((rgb >> 8) & 0xff) / 255;
+  const b = (rgb & 0xff) / 255;
+  const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
+  return lum > 0.5 ? '#1f2430' : '#ffffff';
+}
+
+/** De opgeslagen kleur, of de vertrouwde paarse als er nog niets gekozen is. */
+export function loadColor() {
+  const v = parseInt(localStorage.getItem(KEY) || '', 10);
+  return Number.isFinite(v) && v >= 0 && v <= 0xffffff ? v : FALLBACK;
+}
+
+/**
+ * De kleur toepassen: het accent dat de knoppen, de bubbels en de (+) dragen,
+ * plus een zachte tint over de donkere achtergrond zodat de hele hemel de
+ * gekozen kleur ademt. De tekst-op-accent volgt de luminantie.
+ */
+export function applyColor(rgb) {
+  const root = document.documentElement.style;
+  root.setProperty('--accent', hex(rgb));
+  root.setProperty('--on-accent', onSky(rgb));
+  // De achtergrond blijft donker (leesbaar), maar krijgt een vleugje van de
+  // kleur: een verticale gradient van de getinte nacht naar bijna zwart.
+  const r = (rgb >> 16) & 0xff, g = (rgb >> 8) & 0xff, b = rgb & 0xff;
+  const tint = (f) => `rgb(${Math.round(r * f + 20 * (1 - f))},${Math.round(g * f + 26 * (1 - f))},${Math.round(b * f + 36 * (1 - f))})`;
+  root.setProperty('--bg-top', tint(0.22));
+  root.setProperty('--bg-bottom', tint(0.06));
+}
+
+export function saveColor(rgb) {
+  localStorage.setItem(KEY, String(rgb));
+  applyColor(rgb);
+}
