feat(web): add theme system with Obsidian, Paper, and Midnight themes

Three holistic design directions with CSS custom properties, a theme
store persisted to localStorage, and a live switcher in both the header
(cycle button) and settings page (card selector). Also fixes checkbox
checkmark alignment and adds back navigation from settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-14 22:36:07 +01:00
parent 6c2fc6960a
commit 0352c22b4f
9 changed files with 462 additions and 67 deletions
+18
View File
@@ -1,7 +1,25 @@
<script>
import '../app.css';
import { themeStore } from '$lib/stores/theme.js';
// Subscribe to trigger initialization on mount (sets data-theme attribute)
$: void $themeStore;
</script>
<svelte:head>
<!-- Prevent FOUC: apply theme before first paint -->
{@html `<script>
(function() {
var t = localStorage.getItem('opal-theme');
if (t && ['obsidian','paper','midnight'].includes(t)) {
document.documentElement.dataset.theme = t;
} else {
document.documentElement.dataset.theme = 'obsidian';
}
})();
</` + 'script>'}
</svelte:head>
<div class="app">
<slot />
</div>
+47 -1
View File
@@ -1,5 +1,6 @@
<script>
import { authStore } from '$lib/stores/auth.js';
import ThemeSwitcher from '$lib/components/ThemeSwitcher.svelte';
import { syncStore } from '$lib/stores/sync.js';
import Button from '$lib/components/ui/Button.svelte';
import Input from '$lib/components/ui/Input.svelte';
@@ -75,8 +76,20 @@
<div class="page">
<div class="container">
<h1>Settings</h1>
<div class="page-header">
<a href="/" class="back-link" aria-label="Back to tasks">
<svg class="back-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
</svg>
</a>
<h1>Settings</h1>
</div>
<section class="section">
<h2>Theme</h2>
<ThemeSwitcher mode="full" />
</section>
{#if $authStore.isAuthenticated}
<section class="section">
<h2>Account</h2>
@@ -153,6 +166,39 @@
</div>
<style>
.page-header {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding-top: var(--spacing-lg);
margin-bottom: var(--spacing-md);
}
.page-header h1 {
margin-bottom: 0;
}
.back-link {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: var(--border-radius);
color: var(--text-secondary);
transition: background-color 0.2s;
}
.back-link:hover {
background-color: var(--bg-tertiary);
text-decoration: none;
}
.back-icon {
width: 1.25rem;
height: 1.25rem;
}
.section {
background-color: var(--bg-primary);
border-radius: var(--border-radius);