+
diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx
new file mode 100644
index 00000000..10e7c990
--- /dev/null
+++ b/src/components/Settings/ThemeSettings.tsx
@@ -0,0 +1,49 @@
+import React from 'react'
+import { faSun, faMoon, faDesktop } from '@fortawesome/free-solid-svg-icons'
+import { UserSettings } from '../../services/settingsService'
+import IconButton from '../IconButton'
+
+interface ThemeSettingsProps {
+ settings: UserSettings
+ onUpdate: (updates: Partial
) => void
+}
+
+const ThemeSettings: React.FC = ({ settings, onUpdate }) => {
+ const currentTheme = settings.theme ?? 'system'
+
+ return (
+
+
Theme
+
+
+
+
+ onUpdate({ theme: 'light' })}
+ title="Light theme"
+ ariaLabel="Light theme"
+ variant={currentTheme === 'light' ? 'primary' : 'ghost'}
+ />
+ onUpdate({ theme: 'dark' })}
+ title="Dark theme"
+ ariaLabel="Dark theme"
+ variant={currentTheme === 'dark' ? 'primary' : 'ghost'}
+ />
+ onUpdate({ theme: 'system' })}
+ title="Use system preference"
+ ariaLabel="Use system preference"
+ variant={currentTheme === 'system' ? 'primary' : 'ghost'}
+ />
+
+
+
+ )
+}
+
+export default ThemeSettings
+
diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts
index b78d03da..616f21f2 100644
--- a/src/hooks/useSettings.ts
+++ b/src/hooks/useSettings.ts
@@ -5,6 +5,7 @@ import { EventFactory } from 'applesauce-factory'
import { AccountManager } from 'applesauce-accounts'
import { UserSettings, loadSettings, saveSettings, watchSettings } from '../services/settingsService'
import { loadFont, getFontFamily } from '../utils/fontLoader'
+import { applyTheme } from '../utils/theme'
import { RELAYS } from '../config/relays'
interface UseSettingsParams {
@@ -47,7 +48,10 @@ export function useSettings({ relayPool, eventStore, pubkey, accountManager }: U
const root = document.documentElement.style
const fontKey = settings.readingFont || 'system'
- console.log('🎨 Applying settings styles:', { fontKey, fontSize: settings.fontSize })
+ console.log('🎨 Applying settings styles:', { fontKey, fontSize: settings.fontSize, theme: settings.theme })
+
+ // Apply theme (defaults to 'system' if not set)
+ applyTheme(settings.theme ?? 'system')
// Load font first and wait for it to be ready
if (fontKey !== 'system') {
diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts
index 8b03ef12..f8f4be97 100644
--- a/src/services/settingsService.ts
+++ b/src/services/settingsService.ts
@@ -47,6 +47,8 @@ export interface UserSettings {
imageCacheSizeMB?: number // Maximum cache size in megabytes (default: 210MB)
// Mobile settings
autoCollapseSidebarOnMobile?: boolean // Auto-collapse sidebar on mobile (default: true)
+ // Theme preference
+ theme?: 'dark' | 'light' | 'system' // default: system
}
export async function loadSettings(
diff --git a/src/styles/base/global.css b/src/styles/base/global.css
index 3bb2bcef..f394f898 100644
--- a/src/styles/base/global.css
+++ b/src/styles/base/global.css
@@ -22,7 +22,7 @@ body.mobile-sidebar-open {
justify-content: center;
text-align: center;
padding: 2rem;
- color: rgb(212 212 216); /* zinc-300 */
+ color: var(--color-text);
}
diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css
index e536fae2..cea7cd74 100644
--- a/src/styles/base/variables.css
+++ b/src/styles/base/variables.css
@@ -4,10 +4,6 @@
line-height: 1.5;
font-weight: 400;
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
@@ -47,10 +43,70 @@
--safe-area-right: env(safe-area-inset-right, 0px);
}
+/* Dark theme (default) */
+:root.theme-dark {
+ color-scheme: dark;
+
+ --color-bg: #18181b; /* zinc-900 */
+ --color-bg-elevated: #27272a; /* zinc-800 */
+ --color-bg-subtle: #1e1e1e; /* between zinc-800 and zinc-900 */
+ --color-border: #3f3f46; /* zinc-700 */
+ --color-border-subtle: #52525b; /* zinc-600 */
+ --color-text: #e4e4e7; /* zinc-200 */
+ --color-text-secondary: #a1a1aa; /* zinc-400 */
+ --color-text-muted: #71717a; /* zinc-500 */
+ --color-primary: #6366f1; /* indigo-500 */
+ --color-primary-hover: #4f46e5; /* indigo-600 */
+}
+
+/* Light theme */
+:root.theme-light {
+ color-scheme: light;
+
+ --color-bg: #ffffff; /* white */
+ --color-bg-elevated: #f5f5f5; /* gray-100 */
+ --color-bg-subtle: #fafafa; /* gray-50 */
+ --color-border: #e5e7eb; /* gray-200 */
+ --color-border-subtle: #d1d5db; /* gray-300 */
+ --color-text: #111827; /* gray-900 */
+ --color-text-secondary: #374151; /* gray-700 */
+ --color-text-muted: #6b7280; /* gray-500 */
+ --color-primary: #4f46e5; /* indigo-600 */
+ --color-primary-hover: #4338ca; /* indigo-700 */
+}
+
+/* System theme - follow OS preference */
+:root.theme-system {
+ color-scheme: light dark;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root.theme-system {
+ --color-bg: #18181b;
+ --color-bg-elevated: #27272a;
+ --color-bg-subtle: #1e1e1e;
+ --color-border: #3f3f46;
+ --color-border-subtle: #52525b;
+ --color-text: #e4e4e7;
+ --color-text-secondary: #a1a1aa;
+ --color-text-muted: #71717a;
+ --color-primary: #6366f1;
+ --color-primary-hover: #4f46e5;
+ }
+}
+
@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
+ :root.theme-system {
+ --color-bg: #ffffff;
+ --color-bg-elevated: #f5f5f5;
+ --color-bg-subtle: #fafafa;
+ --color-border: #e5e7eb;
+ --color-border-subtle: #d1d5db;
+ --color-text: #111827;
+ --color-text-secondary: #374151;
+ --color-text-muted: #6b7280;
+ --color-primary: #4f46e5;
+ --color-primary-hover: #4338ca;
}
}
diff --git a/src/styles/components/cards.css b/src/styles/components/cards.css
index 480791cd..d83a53c7 100644
--- a/src/styles/components/cards.css
+++ b/src/styles/components/cards.css
@@ -1,14 +1,14 @@
/* Bookmark item and blog post cards */
-.bookmark-item { background: rgb(24 24 27); /* zinc-900 */ padding: 1.5rem; border-radius: 12px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
+.bookmark-item { background: var(--color-bg); padding: 1.5rem; border-radius: 12px; transition: all 0.2s ease; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); }
.bookmark-item:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); }
-.bookmark-item h3 { margin: 0 0 0.5rem 0; color: rgb(255 255 255); /* white */ font-size: 1.2rem; }
-.bookmark-url { color: rgb(99 102 241); /* indigo-500 */ text-decoration: none; display: block; margin-bottom: 0.5rem; word-break: break-all; background: none; border: none; padding: 0; font: inherit; cursor: pointer; text-align: left; width: 100%; }
+.bookmark-item h3 { margin: 0 0 0.5rem 0; color: var(--color-text); font-size: 1.2rem; }
+.bookmark-url { color: var(--color-primary); text-decoration: none; display: block; margin-bottom: 0.5rem; word-break: break-all; background: none; border: none; padding: 0; font: inherit; cursor: pointer; text-align: left; width: 100%; }
.bookmark-url:hover { text-decoration: underline; }
-.bookmark-content { color: rgb(212 212 216); /* zinc-300 */ margin: 0.5rem 0; line-height: 1.4; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; }
-.bookmark-meta { color: rgb(161 161 170); /* zinc-400 */ font-size: 0.9rem; margin-top: 0.5rem; }
+.bookmark-content { color: var(--color-text); margin: 0.5rem 0; line-height: 1.4; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; }
+.bookmark-meta { color: var(--color-text-secondary); font-size: 0.9rem; margin-top: 0.5rem; }
.individual-bookmarks { margin: 1rem 0; }
-.individual-bookmarks h4 { margin: 0 0 1rem 0; font-size: 1rem; color: rgb(255 255 255); /* white */ }
+.individual-bookmarks h4 { margin: 0 0 1rem 0; font-size: 1rem; color: var(--color-text); }
.bookmarks-grid { display: flex; flex-direction: column; gap: 1rem; width: 100%; max-width: 100%; }
.bookmarks-grid.bookmarks-compact { gap: 0.5rem; }
@@ -19,75 +19,75 @@
.bookmarks-grid.bookmarks-large { gap: 1rem; }
}
-.individual-bookmark { background: transparent; padding: 1rem; border-radius: 8px; transition: all 0.2s ease; border: 1px solid rgb(39 39 42); /* zinc-800 */ word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; overflow: hidden; }
-.individual-bookmark:hover { border-color: rgb(63 63 70); /* zinc-700 */ background: rgb(39 39 42); /* zinc-800 */ }
+.individual-bookmark { background: transparent; padding: 1rem; border-radius: 8px; transition: all 0.2s ease; border: 1px solid var(--color-bg-elevated); word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; overflow: hidden; }
+.individual-bookmark:hover { border-color: var(--color-border); background: var(--color-bg-elevated); }
/* Compact view */
-.individual-bookmark.compact { padding: 0.5rem 0.5rem; background: transparent; border: none; border-bottom: 1px solid rgb(39 39 42); /* zinc-800 */ border-radius: 0; box-shadow: none; width: 100%; max-width: 100%; overflow: hidden; }
-.individual-bookmark.compact:hover { background: rgb(39 39 42); /* zinc-800 */ border-bottom-color: rgb(63 63 70); /* zinc-700 */ transform: none; box-shadow: none; }
+.individual-bookmark.compact { padding: 0.5rem 0.5rem; background: transparent; border: none; border-bottom: 1px solid var(--color-bg-elevated); border-radius: 0; box-shadow: none; width: 100%; max-width: 100%; overflow: hidden; }
+.individual-bookmark.compact:hover { background: var(--color-bg-elevated); border-bottom-color: var(--color-border); transform: none; box-shadow: none; }
.compact-row { display: flex; align-items: center; gap: 0.5rem; height: 28px; width: 100%; min-width: 0; overflow: hidden; }
-.compact-thumbnail { width: 24px; height: 24px; flex-shrink: 0; border-radius: 4px; overflow: hidden; background: rgb(39 39 42); /* zinc-800 */ display: flex; align-items: center; justify-content: center; }
+.compact-thumbnail { width: 24px; height: 24px; flex-shrink: 0; border-radius: 4px; overflow: hidden; background: var(--color-bg-elevated); display: flex; align-items: center; justify-content: center; }
.compact-thumbnail img { width: 100%; height: 100%; object-fit: cover; }
.compact-row.clickable { cursor: pointer; }
.compact-row.clickable:active { opacity: 0.8; }
-.bookmark-type-compact { display: flex; align-items: center; gap: 0.25rem; color: rgb(99 102 241); /* indigo-500 */ font-size: 0.85rem; flex-shrink: 0; }
-.compact-text { flex: 1; min-width: 0; color: rgb(212 212 216); /* zinc-300 */ font-size: 0.85rem; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.bookmark-date-compact { font-size: 0.7rem; color: rgb(113 113 122); /* zinc-500 */ flex-shrink: 0; white-space: nowrap; }
-.compact-read-btn { background: transparent; color: rgb(161 161 170); /* zinc-400 */ border: none; padding: 0; border-radius: 4px; cursor: pointer; font-size: 0.75rem; display: flex; align-items: center; justify-content: center; width: 24px; height: 22px; flex-shrink: 0; transition: color 0.2s ease; }
-.compact-read-btn:hover { color: rgb(212 212 216); /* zinc-300 */ }
+.bookmark-type-compact { display: flex; align-items: center; gap: 0.25rem; color: var(--color-primary); font-size: 0.85rem; flex-shrink: 0; }
+.compact-text { flex: 1; min-width: 0; color: var(--color-text); font-size: 0.85rem; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.bookmark-date-compact { font-size: 0.7rem; color: var(--color-text-muted); flex-shrink: 0; white-space: nowrap; }
+.compact-read-btn { background: transparent; color: var(--color-text-secondary); border: none; padding: 0; border-radius: 4px; cursor: pointer; font-size: 0.75rem; display: flex; align-items: center; justify-content: center; width: 24px; height: 22px; flex-shrink: 0; transition: color 0.2s ease; }
+.compact-read-btn:hover { color: var(--color-text); }
.compact-read-btn:active { transform: translateY(1px); }
.bookmark-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem; flex-wrap: wrap; gap: 0.5rem; }
-.bookmark-type { color: rgb(99 102 241); /* indigo-500 */ font-size: 0.9rem; display: flex; align-items: center; gap: 0.35rem; }
-.bookmark-id { font-family: monospace; font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ background: rgb(24 24 27); /* zinc-900 */ padding: 0.25rem 0.5rem; border-radius: 4px; }
-.bookmark-date { font-size: 0.8rem; color: rgb(113 113 122); /* zinc-500 */ }
-.bookmark-date-link { font-size: 0.8rem; color: rgb(113 113 122); /* zinc-500 */ text-decoration: none; transition: color 0.2s ease; }
-.bookmark-date-link:hover { color: rgb(96 165 250); /* blue-400 */ text-decoration: underline; }
-.individual-bookmark .bookmark-content { margin: 0.75rem 0; color: rgb(212 212 216); /* zinc-300 */ line-height: 1.6; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; }
-.expand-toggle { margin: 0.25rem 0; background: transparent; border: none; color: rgb(161 161 170); /* zinc-400 */ cursor: pointer; width: 100%; height: 22px; display: flex; align-items: center; justify-content: center; }
-.expand-toggle:hover { color: rgb(212 212 216); /* zinc-300 */ }
+.bookmark-type { color: var(--color-primary); font-size: 0.9rem; display: flex; align-items: center; gap: 0.35rem; }
+.bookmark-id { font-family: monospace; font-size: 0.8rem; color: var(--color-text-secondary); background: var(--color-bg); padding: 0.25rem 0.5rem; border-radius: 4px; }
+.bookmark-date { font-size: 0.8rem; color: var(--color-text-muted); }
+.bookmark-date-link { font-size: 0.8rem; color: var(--color-text-muted); text-decoration: none; transition: color 0.2s ease; }
+.bookmark-date-link:hover { color: var(--color-primary); text-decoration: underline; }
+.individual-bookmark .bookmark-content { margin: 0.75rem 0; color: var(--color-text); line-height: 1.6; font-size: 0.9rem; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; }
+.expand-toggle { margin: 0.25rem 0; background: transparent; border: none; color: var(--color-text-secondary); cursor: pointer; width: 100%; height: 22px; display: flex; align-items: center; justify-content: center; }
+.expand-toggle:hover { color: var(--color-text); }
.bookmark-footer { display: flex; justify-content: space-between; align-items: center; margin-top: 0.75rem; gap: 0.75rem; }
-.bookmark-meta-minimal { font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ }
-.author-link-minimal { color: rgb(161 161 170); /* zinc-400 */ text-decoration: none; transition: color 0.2s ease; }
-.author-link-minimal:hover { color: rgb(212 212 216); /* zinc-300 */ }
-.read-now-button-minimal { background: rgb(99 102 241); /* indigo-500 */ color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85rem; transition: all 0.2s ease; white-space: nowrap; }
-.read-now-button-minimal:hover { background: rgb(79 70 229); /* indigo-600 */ }
-.expand-toggle-urls { margin-top: 0.5rem; background: transparent; border: none; color: rgb(99 102 241); /* indigo-500 */ cursor: pointer; font-size: 0.8rem; padding: 0.25rem 0; text-decoration: underline; }
-.expand-toggle-urls:hover { color: rgb(129 140 248); /* indigo-400 */ }
+.bookmark-meta-minimal { font-size: 0.8rem; color: var(--color-text-secondary); }
+.author-link-minimal { color: var(--color-text-secondary); text-decoration: none; transition: color 0.2s ease; }
+.author-link-minimal:hover { color: var(--color-text); }
+.read-now-button-minimal { background: var(--color-primary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85rem; transition: all 0.2s ease; white-space: nowrap; }
+.read-now-button-minimal:hover { background: var(--color-primary-hover); }
+.expand-toggle-urls { margin-top: 0.5rem; background: transparent; border: none; color: var(--color-primary); cursor: pointer; font-size: 0.8rem; padding: 0.25rem 0; text-decoration: underline; }
+.expand-toggle-urls:hover { color: var(--color-primary-hover); }
/* Large preview view */
-.individual-bookmark.large { padding: 0; display: flex; flex-direction: column; overflow: hidden; border: 1px solid rgb(39 39 42); /* zinc-800 */ }
-.large-preview-image { width: 100%; height: 180px; background: rgb(24 24 27); /* zinc-900 */ background-size: cover; background-position: center; background-repeat: no-repeat; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; border-bottom: 1px solid rgb(63 63 70); /* zinc-700 */ position: relative; }
+.individual-bookmark.large { padding: 0; display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--color-bg-elevated); }
+.large-preview-image { width: 100%; height: 180px; background: var(--color-bg); background-size: cover; background-position: center; background-repeat: no-repeat; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; border-bottom: 1px solid var(--color-border); position: relative; }
.large-preview-image:hover { opacity: 0.9; }
.large-preview-image::after { content: ''; position: absolute; inset: 0; background: linear-gradient(to bottom, transparent 60%, rgba(0,0,0,0.3) 100%); pointer-events: none; }
-.preview-placeholder { font-size: 3rem; color: rgb(82 82 91); /* zinc-600 */ }
+.preview-placeholder { font-size: 3rem; color: var(--color-border-subtle); }
.large-content { padding: 1.25rem; }
-.large-text { color: rgb(212 212 216); /* zinc-300 */ font-size: 0.95rem; line-height: 1.6; margin-bottom: 1rem; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
-.large-footer { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ padding-top: 0.75rem; border-top: 1px solid rgb(63 63 70); /* zinc-700 */ }
+.large-text { color: var(--color-text); font-size: 0.95rem; line-height: 1.6; margin-bottom: 1rem; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
+.large-footer { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; font-size: 0.8rem; color: var(--color-text-secondary); padding-top: 0.75rem; border-top: 1px solid var(--color-border); }
.large-author { flex: 1; }
-.large-read-button { background: rgb(99 102 241); /* indigo-500 */ color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85rem; transition: all 0.2s ease; display: flex; align-items: center; gap: 0.5rem; }
-.large-read-button:hover { background: rgb(79 70 229); /* indigo-600 */ }
+.large-read-button { background: var(--color-primary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-weight: 600; font-size: 0.85rem; transition: all 0.2s ease; display: flex; align-items: center; gap: 0.5rem; }
+.large-read-button:hover { background: var(--color-primary-hover); }
/* Blog cards (Explore) */
.explore-container { padding: 2rem; max-width: 1400px; margin: 0 auto; min-height: 100vh; }
.explore-header { text-align: center; margin-bottom: 3rem; }
-.explore-header h1 { font-size: 2.5rem; margin: 0 0 1rem 0; color: rgb(99 102 241); /* indigo-500 */ display: flex; align-items: center; justify-content: center; gap: 1rem; }
-.explore-subtitle { font-size: 1.125rem; color: rgba(255, 255, 255, 0.7); margin: 0; }
-.explore-loading, .explore-error, .explore-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; color: rgba(255, 255, 255, 0.7); }
+.explore-header h1 { font-size: 2.5rem; margin: 0 0 1rem 0; color: var(--color-primary); display: flex; align-items: center; justify-content: center; gap: 1rem; }
+.explore-subtitle { font-size: 1.125rem; color: var(--color-text-secondary); margin: 0; }
+.explore-loading, .explore-error, .explore-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1rem; color: var(--color-text-secondary); }
.explore-loading { min-height: 0; padding: 0.25rem 0; }
.explore-error { color: rgb(239 68 68); /* red-500 */ }
-.explore-empty { color: rgb(161 161 170); /* zinc-400 */ }
+.explore-empty { color: var(--color-text-secondary); }
.explore-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 2rem; margin-top: 2rem; }
-.blog-post-card { background: rgb(24 24 27); /* zinc-900 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 12px; overflow: hidden; transition: all 0.3s ease; cursor: pointer; display: flex; flex-direction: column; height: 100%; }
-.blog-post-card:hover { border-color: rgb(99 102 241); /* indigo-500 */ transform: translateY(-4px); box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15); }
-.blog-post-card-image { width: 100%; height: 200px; overflow: hidden; background: rgb(9 9 11); /* zinc-950 */ display: flex; align-items: center; justify-content: center; }
+.blog-post-card { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 12px; overflow: hidden; transition: all 0.3s ease; cursor: pointer; display: flex; flex-direction: column; height: 100%; }
+.blog-post-card:hover { border-color: var(--color-primary); transform: translateY(-4px); box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15); }
+.blog-post-card-image { width: 100%; height: 200px; overflow: hidden; background: var(--color-bg-subtle); display: flex; align-items: center; justify-content: center; }
.blog-post-card-image img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.3s ease; }
.blog-post-card:hover .blog-post-card-image img { transform: scale(1.05); }
-.blog-post-image-placeholder { font-size: 3rem; color: rgb(82 82 91); /* zinc-600 */ display: flex; align-items: center; justify-content: center; }
+.blog-post-image-placeholder { font-size: 3rem; color: var(--color-border-subtle); display: flex; align-items: center; justify-content: center; }
.blog-post-card-content { padding: 1.5rem; display: flex; flex-direction: column; gap: 1rem; flex: 1; }
-.blog-post-card-title { font-size: 1.25rem; font-weight: 600; margin: 0; color: rgba(255, 255, 255, 0.95); line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
-.blog-post-card-summary { font-size: 0.875rem; color: rgba(255, 255, 255, 0.6); margin: 0; line-height: 1.6; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; flex: 1; }
-.blog-post-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-top: 0.75rem; border-top: 1px solid rgb(63 63 70); /* zinc-700 */ font-size: 0.75rem; color: rgba(255, 255, 255, 0.5); flex-wrap: wrap; }
+.blog-post-card-title { font-size: 1.25rem; font-weight: 600; margin: 0; color: var(--color-text); line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
+.blog-post-card-summary { font-size: 0.875rem; color: var(--color-text-secondary); margin: 0; line-height: 1.6; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; flex: 1; }
+.blog-post-card-meta { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--color-border); font-size: 0.75rem; color: var(--color-text-muted); flex-wrap: wrap; }
.blog-post-card-author, .blog-post-card-date { display: flex; align-items: center; gap: 0.5rem; }
.blog-post-card-author svg, .blog-post-card-date svg { opacity: 0.7; }
@media (max-width: 768px) {
@@ -98,4 +98,3 @@
.blog-post-card-content { padding: 1rem; }
}
-
diff --git a/src/styles/components/forms.css b/src/styles/components/forms.css
index 3d219513..acd972fd 100644
--- a/src/styles/components/forms.css
+++ b/src/styles/components/forms.css
@@ -4,28 +4,28 @@
.setting-label { text-align: left; flex: 1; }
.setting-control { display: flex; justify-content: flex-end; align-items: center; }
.setting-group.setting-inline label { margin-bottom: 0; }
-.setting-group label { display: block; margin-bottom: 0.5rem; color: rgb(212 212 216); /* zinc-300 */ font-weight: 500; text-align: left; }
+.setting-group label { display: block; margin-bottom: 0.5rem; color: var(--color-text); font-weight: 500; text-align: left; }
.setting-buttons { display: flex; align-items: center; gap: 0.5rem; }
.color-picker { display: flex; align-items: center; gap: 0.5rem; }
-.color-swatch { width: 33px; height: 33px; border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; cursor: pointer; transition: all 0.2s; position: relative; }
-.color-swatch:hover { border-color: rgb(161 161 170); /* zinc-400 */ }
-.color-swatch.active { border-color: rgb(99 102 241); /* indigo-500 */ box-shadow: 0 0 0 2px rgb(99 102 241); /* indigo-500 */ }
-.color-swatch.active::after { content: '✓'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: rgb(0 0 0); /* black */ font-size: 0.875rem; font-weight: bold; text-shadow: 0 0 2px rgb(255 255 255); /* white */ }
-.font-size-btn { min-width: 33px; height: 33px; padding: 0; background: transparent; border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; color: rgb(212 212 216); /* zinc-300 */ cursor: pointer; transition: all 0.2s; font-weight: bold; display: flex; align-items: center; justify-content: center; }
-.font-size-btn:hover { background: rgb(63 63 70); /* zinc-700 */ border-color: rgb(113 113 122); /* zinc-500 */ }
-.font-size-btn.active { background: rgb(99 102 241); /* indigo-500 */ border-color: rgb(99 102 241); /* indigo-500 */ color: white; }
+.color-swatch { width: 33px; height: 33px; border: 1px solid var(--color-border-subtle); border-radius: 6px; cursor: pointer; transition: all 0.2s; position: relative; }
+.color-swatch:hover { border-color: var(--color-text-secondary); }
+.color-swatch.active { border-color: var(--color-primary); box-shadow: 0 0 0 2px var(--color-primary); }
+.color-swatch.active::after { content: '✓'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: rgb(0 0 0); font-size: 0.875rem; font-weight: bold; text-shadow: 0 0 2px rgb(255 255 255); }
+.font-size-btn { min-width: 33px; height: 33px; padding: 0; background: transparent; border: 1px solid var(--color-border-subtle); border-radius: 6px; color: var(--color-text); cursor: pointer; transition: all 0.2s; font-weight: bold; display: flex; align-items: center; justify-content: center; }
+.font-size-btn:hover { background: var(--color-border); border-color: var(--color-text-muted); }
+.font-size-btn.active { background: var(--color-primary); border-color: var(--color-primary); color: white; }
.setting-preview {
margin: 1.5rem 0;
padding: 1rem;
- background: rgb(24 24 27); /* zinc-900 */
- border: 1px solid rgb(63 63 70); /* zinc-700 */
+ background: var(--color-bg);
+ border: 1px solid var(--color-border);
border-radius: 8px;
max-width: 100%;
overflow: hidden;
}
-.preview-label { font-size: 0.875rem; color: rgb(161 161 170); /* zinc-400 */ margin-bottom: 0.75rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; }
+.preview-label { font-size: 0.875rem; color: var(--color-text-secondary); margin-bottom: 0.75rem; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; }
.preview-content {
- color: rgb(228 228 231); /* zinc-200 */
+ color: var(--color-text);
line-height: 1.7;
max-width: 100%;
overflow-wrap: break-word;
@@ -35,20 +35,20 @@
.preview-content h3 {
margin: 0 0 1rem 0;
font-size: 1.5em;
- color: rgb(255 255 255); /* white */
+ color: var(--color-text);
word-wrap: break-word;
}
.preview-content p {
margin: 0.75rem 0;
word-wrap: break-word;
}
-.setting-select { width: 100%; padding: 0.5rem; background: rgb(39 39 42); /* zinc-800 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 4px; color: rgb(255 255 255); /* white */ font-size: 1rem; }
+.setting-select { width: 100%; padding: 0.5rem; background: var(--color-bg-elevated); border: 1px solid var(--color-border-subtle); border-radius: 4px; color: var(--color-text); font-size: 1rem; }
.setting-inline .setting-select { width: auto; min-width: 200px; flex: 1; }
-.setting-select:focus { outline: none; border-color: rgb(99 102 241); /* indigo-500 */ }
+.setting-select:focus { outline: none; border-color: var(--color-primary); }
.font-select option { padding: 0.5rem; font-size: 1rem; }
.checkbox-label { display: flex !important; align-items: center; gap: 0.75rem; cursor: pointer; user-select: none; text-align: left; justify-content: flex-start; margin-bottom: 0 !important; font-weight: normal !important; }
-.setting-checkbox { width: 18px; height: 18px; cursor: pointer; flex-shrink: 0; margin: 0; accent-color: rgb(99 102 241); /* indigo-500 */ }
-.checkbox-label span { color: rgb(228 228 231); /* zinc-200 */ text-align: left; font-weight: 500; }
+.setting-checkbox { width: 18px; height: 18px; cursor: pointer; flex-shrink: 0; margin: 0; accent-color: var(--color-primary); }
+.checkbox-label span { color: var(--color-text); text-align: left; font-weight: 500; }
/* Mobile responsive styles */
@media (max-width: 768px) {
@@ -81,4 +81,3 @@
}
}
-
diff --git a/src/styles/layout/app.css b/src/styles/layout/app.css
index 6b5ff94a..a1dbc9b3 100644
--- a/src/styles/layout/app.css
+++ b/src/styles/layout/app.css
@@ -111,7 +111,7 @@
max-width: 320px;
height: 100vh;
height: 100dvh;
- background: rgb(24 24 27); /* zinc-900 */
+ background: var(--color-bg);
z-index: 1001; /* Above backdrop */
transition: transform 0.3s ease;
box-shadow: none;
diff --git a/src/utils/theme.ts b/src/utils/theme.ts
new file mode 100644
index 00000000..44907f17
--- /dev/null
+++ b/src/utils/theme.ts
@@ -0,0 +1,64 @@
+export type Theme = 'dark' | 'light' | 'system'
+
+let mediaQueryListener: ((e: MediaQueryListEvent) => void) | null = null
+
+/**
+ * Get the system's current theme preference
+ */
+export function getSystemTheme(): 'dark' | 'light' {
+ if (typeof window === 'undefined') return 'dark'
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
+}
+
+/**
+ * Apply theme to the document root element
+ * Handles 'system' theme by listening to OS preference changes
+ */
+export function applyTheme(theme: Theme): void {
+ const root = document.documentElement
+
+ // Remove existing theme classes
+ root.classList.remove('theme-dark', 'theme-light', 'theme-system')
+
+ // Clean up previous media query listener if exists
+ if (mediaQueryListener) {
+ window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', mediaQueryListener)
+ mediaQueryListener = null
+ }
+
+ if (theme === 'system') {
+ root.classList.add('theme-system')
+
+ // Listen for system theme changes
+ mediaQueryListener = (e: MediaQueryListEvent) => {
+ console.log('🎨 System theme changed to:', e.matches ? 'dark' : 'light')
+ // The CSS media query handles the color changes
+ // We just need to update localStorage for no-FOUC on next load
+ localStorage.setItem('theme-system-current', e.matches ? 'dark' : 'light')
+ }
+
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', mediaQueryListener)
+
+ // Store current system theme for no-FOUC on next boot
+ localStorage.setItem('theme-system-current', getSystemTheme())
+ } else {
+ root.classList.add(`theme-${theme}`)
+ }
+
+ // Persist to localStorage for early boot application
+ localStorage.setItem('theme', theme)
+
+ console.log('🎨 Applied theme:', theme)
+}
+
+/**
+ * Get the current theme from localStorage or default to 'system'
+ */
+export function getStoredTheme(): Theme {
+ const stored = localStorage.getItem('theme')
+ if (stored === 'dark' || stored === 'light' || stored === 'system') {
+ return stored
+ }
+ return 'system'
+}
+