fix+feat: correct light/dark palette scoping, add theme toggle
Fix: palette overrides now use @media(prefers-color-scheme:light) + [data-theme=light] only — no more :root:not([data-theme=dark]) which incorrectly matched OS-dark with no attribute set. Feat: theme toggle button in nav (🌙/☀️), persisted to localStorage, applied via data-theme attribute on <html>. Respects OS preference on first visit.
This commit is contained in:
50
src/app.css
50
src/app.css
@@ -18,21 +18,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Warm neutral palette — light mode only.
|
/* Warm neutral palette — light mode only.
|
||||||
Dark mode falls back to Pico's own dark tokens so text is always legible. */
|
Applied when: (a) OS is light and no data-theme override, or (b) explicit light override.
|
||||||
:root:not([data-theme=dark]),
|
Dark mode (OS or explicit) falls back entirely to Pico's own dark tokens. */
|
||||||
[data-theme=light] {
|
|
||||||
--pico-background-color: #F7F5F2;
|
|
||||||
--pico-card-background-color: #FDFCFB;
|
|
||||||
--pico-card-border-color: #E8E4DF;
|
|
||||||
--pico-color: #2C2C2C;
|
|
||||||
--pico-muted-color: #888880;
|
|
||||||
--color-label: #555550;
|
|
||||||
--pico-muted-border-color: #E0DCD7;
|
|
||||||
--pico-primary: #5B7FA6;
|
|
||||||
--pico-primary-background: rgba(91, 127, 166, 0.08);
|
|
||||||
--pico-secondary-background: rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root:not([data-theme]) {
|
:root:not([data-theme]) {
|
||||||
--pico-background-color: #F7F5F2;
|
--pico-background-color: #F7F5F2;
|
||||||
@@ -48,15 +35,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --color-label fallback for dark mode (Pico doesn't define this var) */
|
[data-theme=light] {
|
||||||
|
--pico-background-color: #F7F5F2;
|
||||||
|
--pico-card-background-color: #FDFCFB;
|
||||||
|
--pico-card-border-color: #E8E4DF;
|
||||||
|
--pico-color: #2C2C2C;
|
||||||
|
--pico-muted-color: #888880;
|
||||||
|
--color-label: #555550;
|
||||||
|
--pico-muted-border-color: #E0DCD7;
|
||||||
|
--pico-primary: #5B7FA6;
|
||||||
|
--pico-primary-background: rgba(91, 127, 166, 0.08);
|
||||||
|
--pico-secondary-background: rgba(0, 0, 0, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --color-label: Pico doesn't define this var, so we must set it for dark too */
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root:not([data-theme]) {
|
:root:not([data-theme]) { --color-label: #a0a09a; }
|
||||||
--color-label: #a0a09a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[data-theme=dark] {
|
|
||||||
--color-label: #a0a09a;
|
|
||||||
}
|
}
|
||||||
|
[data-theme=dark] { --color-label: #a0a09a; }
|
||||||
|
|
||||||
/* ── Results grid ──────────────────────────────────────────────────────────── */
|
/* ── Results grid ──────────────────────────────────────────────────────────── */
|
||||||
.results-grid {
|
.results-grid {
|
||||||
@@ -231,3 +227,13 @@
|
|||||||
color: var(--pico-muted-color, #666);
|
color: var(--pico-muted-color, #666);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Theme toggle button ───────────────────────────────────────────────────── */
|
||||||
|
.theme-toggle {
|
||||||
|
width: auto;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: var(--pico-border-radius);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '@picocss/pico/css/pico.min.css';
|
import '@picocss/pico/css/pico.min.css';
|
||||||
import '../app.css';
|
import '../app.css';
|
||||||
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
|
// Resolve initial theme: stored pref > OS preference
|
||||||
|
function resolveInitialTheme(): 'light' | 'dark' {
|
||||||
|
if (!browser) return 'light';
|
||||||
|
const stored = localStorage.getItem('theme');
|
||||||
|
if (stored === 'light' || stored === 'dark') return stored;
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
let theme = $state<'light' | 'dark'>(resolveInitialTheme());
|
||||||
|
|
||||||
|
// Apply theme to <html> and persist
|
||||||
|
$effect(() => {
|
||||||
|
if (!browser) return;
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
localStorage.setItem('theme', theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
theme = theme === 'light' ? 'dark' : 'light';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
@@ -14,6 +36,11 @@
|
|||||||
<li><a href="/">Converter</a></li>
|
<li><a href="/">Converter</a></li>
|
||||||
<li><a href="/about">About</a></li>
|
<li><a href="/about">About</a></li>
|
||||||
<li><a href="/contact">Contact</a></li>
|
<li><a href="/contact">Contact</a></li>
|
||||||
|
<li>
|
||||||
|
<button class="theme-toggle outline secondary" onclick={toggleTheme} aria-label="Toggle light/dark mode">
|
||||||
|
{theme === 'light' ? '🌙' : '☀️'}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Reference in New Issue
Block a user