diff --git a/CHANGELOG.md b/CHANGELOG.md index c50a398..9a2974d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.20.28] - 2026-04-20 + +### Fixed +- Dark mode "System" setting now reliably follows the OS preference on every page load, even in browsers where JavaScript `matchMedia` is restricted (e.g. Brave with fingerprint protection); CSS `@media (prefers-color-scheme: dark)` now serves as the authoritative source for system preference detection instead of JS + ## [0.20.27] - 2026-04-20 ### Fixed diff --git a/package-lock.json b/package-lock.json index 9d28d66..f2d06ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oikos", - "version": "0.20.27", + "version": "0.20.28", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oikos", - "version": "0.20.27", + "version": "0.20.28", "license": "MIT", "dependencies": { "bcrypt": "^6.0.0", diff --git a/package.json b/package.json index fe246fe..15b404b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.20.27", + "version": "0.20.28", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/index.html b/public/index.html index 8d2021c..f878f22 100644 --- a/public/index.html +++ b/public/index.html @@ -40,24 +40,18 @@ - + diff --git a/public/pages/settings.js b/public/pages/settings.js index d561448..cbe4479 100644 --- a/public/pages/settings.js +++ b/public/pages/settings.js @@ -851,11 +851,13 @@ function currentTheme() { function applyTheme(value) { localStorage.setItem('oikos-theme', value); - if (value === 'light' || value === 'dark') { - document.documentElement.setAttribute('data-theme', value); + if (value === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + } else if (value === 'light') { + document.documentElement.setAttribute('data-theme', 'light'); } else { - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light'); + document.documentElement.removeAttribute('data-theme'); + // tokens.css @media (prefers-color-scheme: dark) übernimmt sofort } } diff --git a/public/styles/tokens.css b/public/styles/tokens.css index 46fba92..3657df6 100644 --- a/public/styles/tokens.css +++ b/public/styles/tokens.css @@ -415,10 +415,105 @@ /* ================================================================ * Dark Mode — private Tokens überschreiben, öffentliche API bleibt stabil. * - * data-theme="dark" wird durch das Head-Script in index.html gesetzt — - * sowohl für manuelle Overrides als auch für System-Präferenz (via - * matchMedia-Listener). Ein einziger Selektor reicht, kein @media-Duplikat. + * Zwei Selektoren aktivieren Dark Mode: + * 1. @media (prefers-color-scheme: dark) — System-Präferenz (CSS-native, + * kein JS erforderlich, funktioniert auch wenn matchMedia blockiert ist) + * 2. [data-theme="dark"] — expliziter Override durch den Nutzer + * + * Explizites Light ([data-theme="light"]) blockiert den @media-Block via :not(). + * Explizites Dark ([data-theme="dark"]) überschreibt den @media-Block, falls OS + * auf Light steht. * ================================================================ */ +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + /* Neutral-Skala invertiert (warm-dunkel) */ + --_neutral-50: #1A1A18; + --_neutral-100: #222220; + --_neutral-150: #2A2A28; + --_neutral-200: #333331; + --_neutral-250: #3D3D3A; + --_neutral-300: #48484A; + --_neutral-400: #636360; + --_neutral-500: #8E8D89; + --_neutral-600: #AEADB0; + --_neutral-700: #C8C7C3; + --_neutral-800: #E2E1DC; + --_neutral-900: #F5F4F1; + --_neutral-950: #FAFAF8; + + --_color-surface: #2A2A28; + --_color-surface-3: #333331; + + --_sidebar-bg: #1A1A18; + --_sidebar-shadow-light: rgba(255, 255, 255, 0.04); + --_sidebar-shadow-dark: rgba(0, 0, 0, 0.4); + + --_color-accent: #818CF8; + --_color-accent-hover: #6366F1; + --_color-accent-active: #4F46E5; + --_color-accent-light: #2E2D5B; + --_color-accent-subtle: #252255; + --_color-btn-primary: #6366F1; + --_color-btn-primary-hover: #4F46E5; + --_color-accent-secondary: #A78BFA; + + --_color-success: #4ADE80; + --_color-warning: #F59E0B; + --_color-danger: #FCA5A5; + --_color-text-tertiary: #A3A3A0; + --_color-success-light: #1A3325; + --_color-warning-light: #332400; + --_color-danger-light: #3D1C1A; + --_color-info-light: #1A2D40; + + --_module-dashboard: #818CF8; + --_module-tasks: #4ADE80; + --_module-calendar: #A78BFA; + --_module-meals: #FB923C; + --_module-shopping: #F472B6; + --_module-notes: #FCD34D; + --_module-contacts: #60A5FA; + --_module-budget: #2DD4BF; + --_module-settings: #94A3B8; + + --_meal-breakfast: #F59E0B; + --_meal-breakfast-light: #332400; + --_meal-lunch-light: #1A3325; + --_meal-dinner: #818CF8; + --_meal-dinner-light: #2E2D5B; + --_meal-snack-light: #3D2010; + + --_color-priority-none-bg: rgba(142, 141, 137, 0.12); + --_color-priority-low-bg: rgba(142, 141, 137, 0.18); + --_color-priority-medium-bg: rgba(230, 147, 10, 0.18); + --_color-priority-high-bg: rgba(212, 81, 30, 0.18); + --_color-priority-urgent-bg: rgba(229, 83, 75, 0.18); + + --_color-overlay: rgba(0, 0, 0, 0.6); + --_color-overlay-light: rgba(0, 0, 0, 0.35); + + --_shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25); + --_shadow-md: 0 4px 12px rgba(0, 0, 0, 0.35); + --_shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.45); + + --_glass-bg: rgba(40, 40, 38, 0.75); + --_glass-bg-hover: rgba(50, 50, 48, 0.82); + --_glass-bg-elevated: rgba(58, 58, 55, 0.90); + --_glass-border: rgba(255, 255, 255, 0.12); + --_glass-border-subtle: rgba(255, 255, 255, 0.07); + --_glass-highlight: rgba(255, 255, 255, 0.10); + --_glass-highlight-subtle: rgba(255, 255, 255, 0.06); + --_glass-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.30), 0 0 0 1px rgba(255, 255, 255, 0.08); + --_glass-shadow-md: 0 4px 20px rgba(0, 0, 0, 0.40), 0 0 0 1px rgba(255, 255, 255, 0.07); + --_glass-shadow-lg: 0 8px 40px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.06); + --_glass-bg-card: rgba(38, 38, 36, 0.50); + --_glass-bg-card-hover: rgba(48, 48, 46, 0.62); + --_glass-bg-input: rgba(34, 34, 32, 0.45); + --_glass-bg-toolbar: rgba(40, 40, 38, 0.55); + --_glass-tint-strength: 8%; + } +} + [data-theme="dark"] { /* Neutral-Skala invertiert (warm-dunkel) */ --_neutral-50: #1A1A18;