From 71b649b61e413df75d3a4da4155ccc9d60343ae7 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Sun, 9 Nov 2025 21:16:33 -0500 Subject: PodcastItLater: Fix dark mode - use automatic CSS-only approach - Removed JavaScript theme switcher (not needed) - Removed toggle buttons from all pages - Added automatic dark mode CSS based on prefers-color-scheme media query - Added color-scheme meta tag for native UI hints - Uses inline CSS instead of external file (build system constraint) - Zero JavaScript - pure CSS solution - Automatically follows system/browser dark mode preference Task: t-64tkB5 --- Biz/PodcastItLater/Web.py | 181 ++++++++++++++++------------------------------ 1 file changed, 63 insertions(+), 118 deletions(-) (limited to 'Biz') diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index 39e2240..d91587a 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -53,55 +53,34 @@ from typing import override logger = Log.setup() -def create_theme_switcher_script() -> html.script: - """Create JavaScript for theme switching with localStorage persistence.""" - return html.script( +def create_auto_dark_mode_style() -> html.style: + """Create CSS for automatic dark mode based on prefers-color-scheme.""" + return html.style( """ - // Theme switcher with localStorage persistence - (function() { - const getStoredTheme = () => localStorage.getItem('theme'); - const setStoredTheme = (theme) => { - localStorage.setItem('theme', theme); - }; - const getPreferredTheme = () => { - const storedTheme = getStoredTheme(); - if (storedTheme) { - return storedTheme; - } - return window.matchMedia('(prefers-color-scheme: dark)').matches - ? 'dark' : 'light'; - }; - - const setTheme = theme => { - document.documentElement.setAttribute('data-bs-theme', theme); - const icon = document.getElementById('theme-icon'); - if (icon) { - icon.className = theme === 'dark' - ? 'bi bi-sun-fill' - : 'bi bi-moon-fill'; - } - }; - - // Set theme on page load - setTheme(getPreferredTheme()); - - // Theme toggle button handler - window.toggleTheme = () => { - const currentTheme = document.documentElement - .getAttribute('data-bs-theme'); - const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; - setTheme(newTheme); - setStoredTheme(newTheme); - }; - - // Listen for system theme changes - window.matchMedia('(prefers-color-scheme: dark)') - .addEventListener('change', e => { - if (!getStoredTheme()) { - setTheme(e.matches ? 'dark' : 'light'); - } - }); - })(); + /* Auto dark mode - sets Bootstrap theme based on system preference */ + @media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + } + html { + --bs-body-color: #dee2e6; + --bs-body-bg: #212529; + --bs-emphasis-color: #fff; + --bs-border-color: #495057; + --bs-link-color: #6ea8fe; + --bs-link-hover-color: #8bb9fe; + --bs-code-color: #e685b5; + } + body { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + } + } + @media (prefers-color-scheme: light) { + :root { + color-scheme: light; + } + } """, ) @@ -687,6 +666,10 @@ class BillingPage(Component[AnyChildren, BillingPageAttrs]): name="viewport", content="width=device-width, initial-scale=1", ), + html.meta( + name="color-scheme", + content="light dark", + ), html.title("Billing - PodcastItLater"), html.script( src="https://unpkg.com/htmx.org@1.9.10", @@ -701,51 +684,32 @@ class BillingPage(Component[AnyChildren, BillingPageAttrs]): "@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons" "@1.11.3/font/bootstrap-icons.min.css');", ), + # Auto dark mode CSS (must come after Bootstrap) + create_auto_dark_mode_style(), html.div( html.div( + html.h1( + html.i( + classes=["bi", "bi-credit-card", "me-2"], + ), + "Billing & Usage", + classes=["mb-4"], + ), html.div( - html.h1( + html.a( html.i( - classes=["bi", "bi-credit-card", "me-2"], - ), - "Billing & Usage", - classes=["mb-4"], - ), - html.div( - html.a( - html.i( - classes=["bi", "bi-arrow-left", "me-1"], - ), - "Back to Dashboard", - href="/", - classes=[ - "btn", - "btn-outline-secondary", - "mb-4", - ], + classes=["bi", "bi-arrow-left", "me-1"], ), + "Back to Dashboard", + href="/", + classes=[ + "btn", + "btn-outline-secondary", + "mb-4", + ], ), - classes=["flex-grow-1"], - ), - html.button( - html.i( - id="theme-icon", - classes=["bi", "bi-moon-fill"], - ), - on_click="toggleTheme()", - classes=[ - "btn", - "btn-outline-secondary", - "position-absolute", - "top-0", - "end-0", - "mt-3", - "me-3", - ], - title="Toggle dark mode", - type="button", ), - classes=["position-relative", "mb-4"], + classes=["mb-4"], ), # Success/Error alerts html.div( @@ -882,8 +846,6 @@ class BillingPage(Component[AnyChildren, BillingPageAttrs]): integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL", crossorigin="anonymous", ), - # Theme switcher script - create_theme_switcher_script(), ), ) @@ -999,6 +961,10 @@ class HomePage(Component[AnyChildren, HomePageAttrs]): name="viewport", content="width=device-width, initial-scale=1", ), + html.meta( + name="color-scheme", + content="light dark", + ), html.title("PodcastItLater"), html.script( src="https://unpkg.com/htmx.org@1.9.10", @@ -1013,40 +979,21 @@ class HomePage(Component[AnyChildren, HomePageAttrs]): "@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons" "@1.11.3/font/bootstrap-icons.min.css');", ), + # Auto dark mode CSS (must come after Bootstrap) + create_auto_dark_mode_style(), # Bootstrap container html.div( # Header html.div( - html.div( - html.h1( - "PodcastItLater", - classes=["display-4", "mb-2"], - ), - html.p( - "Convert web articles to podcast episodes", - classes=["lead", "text-muted"], - ), - classes=["text-center", "flex-grow-1"], + html.h1( + "PodcastItLater", + classes=["display-4", "mb-2"], ), - html.button( - html.i( - id="theme-icon", - classes=["bi", "bi-moon-fill"], - ), - on_click="toggleTheme()", - classes=[ - "btn", - "btn-outline-secondary", - "position-absolute", - "top-0", - "end-0", - "mt-3", - "me-3", - ], - title="Toggle dark mode", - type="button", + html.p( + "Convert web articles to podcast episodes", + classes=["lead", "text-muted"], ), - classes=["position-relative", "mb-4", "pt-4"], + classes=["text-center", "mb-4", "pt-4"], ), # Error alert html.div( @@ -1180,8 +1127,6 @@ class HomePage(Component[AnyChildren, HomePageAttrs]): integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL", crossorigin="anonymous", ), - # Theme switcher script - create_theme_switcher_script(), ), ) -- cgit v1.2.3