From 994d834a0b066c2ca3afbdc95e04ce40a02f43ee Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:11:38 +0200 Subject: [PATCH 01/23] feat(theme): add CSS variable tokens and theme classes - Define semantic color tokens (--color-bg, --color-text, etc.) - Add .theme-dark, .theme-light, .theme-system CSS classes - Create theme.ts utility for theme application - Add early boot theme script to prevent FOUC - Support system preference with live updates --- index.html | 17 ++++ src/components/Settings.tsx | 2 + src/components/Settings/ThemeSettings.tsx | 49 +++++++++++ src/hooks/useSettings.ts | 6 +- src/services/settingsService.ts | 2 + src/styles/base/global.css | 2 +- src/styles/base/variables.css | 70 ++++++++++++++-- src/styles/components/cards.css | 99 +++++++++++------------ src/styles/components/forms.css | 35 ++++---- src/styles/layout/app.css | 2 +- src/utils/theme.ts | 64 +++++++++++++++ 11 files changed, 270 insertions(+), 78 deletions(-) create mode 100644 src/components/Settings/ThemeSettings.tsx create mode 100644 src/utils/theme.ts diff --git a/index.html b/index.html index b6ff2136..c7ca0399 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,23 @@ + + +
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index e343fad6..c7f3e5cc 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -4,6 +4,7 @@ import { RelayPool } from 'applesauce-relay' import { UserSettings } from '../services/settingsService' import IconButton from './IconButton' import { loadFont } from '../utils/fontLoader' +import ThemeSettings from './Settings/ThemeSettings' import ReadingDisplaySettings from './Settings/ReadingDisplaySettings' import LayoutNavigationSettings from './Settings/LayoutNavigationSettings' import StartupPreferencesSettings from './Settings/StartupPreferencesSettings' @@ -159,6 +160,7 @@ const Settings: React.FC = ({ settings, onSave, onClose, relayPoo
+ 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' +} + From 1ae76031f3247e5054f69d003d58a616db9bfbbb Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:13:42 +0200 Subject: [PATCH 02/23] refactor(css): migrate cards/forms/layout to semantic tokens - Replace hard-coded colors with CSS variables in cards.css - Update forms.css, settings.css, modals.css with tokens - Migrate sidebar.css and highlights.css to use semantic colors - Update layout/app.css and base/global.css - Enables proper light/dark theme switching --- src/styles/components/modals.css | 27 ++++++++------- src/styles/components/settings.css | 43 ++++++++++++------------ src/styles/layout/highlights.css | 53 +++++++++++++++--------------- src/styles/layout/sidebar.css | 39 +++++++++++----------- 4 files changed, 79 insertions(+), 83 deletions(-) diff --git a/src/styles/components/modals.css b/src/styles/components/modals.css index 37ef38aa..6e357a0a 100644 --- a/src/styles/components/modals.css +++ b/src/styles/components/modals.css @@ -1,28 +1,27 @@ /* Add Bookmark Modal */ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.75); display: flex; align-items: center; justify-content: center; z-index: 10000; padding: 1rem; } -.modal-content { background: rgb(24 24 27); /* zinc-900 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 12px; max-width: 500px; width: 100%; max-height: 90vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); box-sizing: border-box; } +.modal-content { background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 12px; max-width: 500px; width: 100%; max-height: 90vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); box-sizing: border-box; } @media (max-width: 768px) { .modal-overlay { padding: 0; align-items: flex-end; } .modal-content { max-width: 100%; max-height: 95vh; max-height: 95dvh; border-radius: 16px 16px 0 0; margin: 0; padding-bottom: var(--safe-area-bottom); } } -.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1.5rem; border-bottom: 1px solid rgb(63 63 70); /* zinc-700 */ } -.modal-header h2 { margin: 0; font-size: 1.5rem; color: rgb(255 255 255); /* white */ } +.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 1.5rem; border-bottom: 1px solid var(--color-border); } +.modal-header h2 { margin: 0; font-size: 1.5rem; color: var(--color-text); } .modal-form { padding: 1.5rem; } .form-group { margin-bottom: 1.25rem; } -.form-group label { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; color: rgb(212 212 216); /* zinc-300 */ font-size: 0.9rem; font-weight: 500; } -.fetching-indicator { font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ font-weight: normal; display: inline-flex; align-items: center; gap: 0.5rem; } -.form-group input, .form-group textarea { width: 100%; padding: 0.75rem; background: rgb(39 39 42); /* zinc-800 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; color: rgb(255 255 255); /* white */ font-size: 1rem; font-family: inherit; transition: border-color 0.2s; box-sizing: border-box; } -.form-group input:focus, .form-group textarea:focus { outline: none; border-color: rgb(99 102 241); /* indigo-500 */ } +.form-group label { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; color: var(--color-text); font-size: 0.9rem; font-weight: 500; } +.fetching-indicator { font-size: 0.8rem; color: var(--color-text-secondary); font-weight: normal; display: inline-flex; align-items: center; gap: 0.5rem; } +.form-group input, .form-group textarea { width: 100%; padding: 0.75rem; background: var(--color-bg-elevated); border: 1px solid var(--color-border-subtle); border-radius: 6px; color: var(--color-text); font-size: 1rem; font-family: inherit; transition: border-color 0.2s; box-sizing: border-box; } +.form-group input:focus, .form-group textarea:focus { outline: none; border-color: var(--color-primary); } .form-group input:disabled, .form-group textarea:disabled { opacity: 0.6; cursor: not-allowed; } .form-group textarea { resize: vertical; min-height: 80px; } -.form-helper-text { margin-top: 0.25rem; font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ line-height: 1.4; } -.modal-error { padding: 0.75rem; background: rgba(220, 53, 69, 0.1); border: 1px solid rgb(220 38 38); /* red-600 */ border-radius: 6px; color: rgb(220 38 38); /* red-600 */ font-size: 0.9rem; margin-bottom: 1rem; } +.form-helper-text { margin-top: 0.25rem; font-size: 0.8rem; color: var(--color-text-secondary); line-height: 1.4; } +.modal-error { padding: 0.75rem; background: rgba(220, 53, 69, 0.1); border: 1px solid rgb(220 38 38); border-radius: 6px; color: rgb(220 38 38); font-size: 0.9rem; margin-bottom: 1rem; } .modal-actions { display: flex; gap: 0.75rem; justify-content: flex-end; margin-top: 1.5rem; } -.btn-secondary { padding: 0.75rem 1.5rem; background: rgb(39 39 42); /* zinc-800 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; color: rgb(212 212 216); /* zinc-300 */ font-size: 1rem; cursor: pointer; transition: all 0.2s; } -.btn-secondary:hover:not(:disabled) { background: rgb(63 63 70); /* zinc-700 */ border-color: rgb(99 102 241); /* indigo-500 */ color: white; } +.btn-secondary { padding: 0.75rem 1.5rem; background: var(--color-bg-elevated); border: 1px solid var(--color-border-subtle); border-radius: 6px; color: var(--color-text); font-size: 1rem; cursor: pointer; transition: all 0.2s; } +.btn-secondary:hover:not(:disabled) { background: var(--color-border); border-color: var(--color-primary); color: var(--color-text); } .btn-secondary:disabled { opacity: 0.6; cursor: not-allowed; } -.btn-primary { padding: 0.75rem 1.5rem; background: rgb(99 102 241); /* indigo-500 */ border: none; border-radius: 6px; color: white; font-size: 1rem; cursor: pointer; transition: background-color 0.2s; } -.btn-primary:hover:not(:disabled) { background: rgb(79 70 229); /* indigo-600 */ } +.btn-primary { padding: 0.75rem 1.5rem; background: var(--color-primary); border: none; border-radius: 6px; color: white; font-size: 1rem; cursor: pointer; transition: background-color 0.2s; } +.btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); } .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } - diff --git a/src/styles/components/settings.css b/src/styles/components/settings.css index a74f5d50..ea1927c2 100644 --- a/src/styles/components/settings.css +++ b/src/styles/components/settings.css @@ -6,37 +6,37 @@ .settings-content { overflow-y: auto; flex: 1; margin-bottom: 1rem; text-align: left; padding: 0 0.25rem 2rem 0.25rem; } .settings-section { margin-bottom: 2.5rem; } .settings-section:last-child { margin-bottom: 0; } -.section-title { font-size: 1rem; font-weight: 600; color: rgb(255 255 255); /* white */ margin: 0 0 1rem 0; padding-bottom: 0.5rem; border-bottom: 1px solid rgb(63 63 70); /* zinc-700 */ text-transform: uppercase; letter-spacing: 0.05em; } +.section-title { font-size: 1rem; font-weight: 600; color: var(--color-text); margin: 0 0 1rem 0; padding-bottom: 0.5rem; border-bottom: 1px solid var(--color-border); text-transform: uppercase; letter-spacing: 0.05em; } .settings-footer { display: flex; justify-content: flex-start; padding: 1rem 0 0.5rem 0; flex-shrink: 0; } -.settings-footer .btn-primary { background: rgb(99 102 241); /* indigo-500 */ color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; font-size: 1rem; cursor: pointer; transition: background-color 0.2s; display: flex; align-items: center; gap: 0.5rem; } -.settings-footer .btn-primary:hover:not(:disabled) { background: rgb(79 70 229); /* indigo-600 */ } +.settings-footer .btn-primary { background: var(--color-primary); color: white; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; font-size: 1rem; cursor: pointer; transition: background-color 0.2s; display: flex; align-items: center; gap: 0.5rem; } +.settings-footer .btn-primary:hover:not(:disabled) { background: var(--color-primary-hover); } .settings-footer .btn-primary:disabled { opacity: 0.6; cursor: not-allowed; } /* Setting groups */ .setting-group { margin-bottom: 1.5rem; } -.setting-label { display: block; margin-bottom: 0.75rem; font-size: 0.9rem; font-weight: 500; color: rgb(212 212 216); /* zinc-300 */ } +.setting-label { display: block; margin-bottom: 0.75rem; font-size: 0.9rem; font-weight: 500; color: var(--color-text); } /* Zap splits preset buttons */ .zap-preset-buttons { display: flex; gap: 0.5rem; flex-wrap: wrap; } .zap-preset-btn { padding: 0.625rem 1.25rem; - background: rgb(39 39 42); /* zinc-800 */ - border: 1px solid rgb(82 82 91); /* zinc-600 */ + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-subtle); border-radius: 6px; - color: rgb(212 212 216); /* zinc-300 */ + color: var(--color-text); font-size: 0.9rem; cursor: pointer; transition: all 0.2s ease; font-weight: 500; } .zap-preset-btn:hover { - background: rgb(63 63 70); /* zinc-700 */ - border-color: rgb(99 102 241); /* indigo-500 */ - color: rgb(255 255 255); /* white */ + background: var(--color-border); + border-color: var(--color-primary); + color: var(--color-text); } .zap-preset-btn.active { - background: rgb(99 102 241); /* indigo-500 */ - border-color: rgb(99 102 241); /* indigo-500 */ + background: var(--color-primary); + border-color: var(--color-primary); color: rgb(255 255 255); /* white */ } @@ -47,14 +47,14 @@ justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.85rem; - color: rgb(161 161 170); /* zinc-400 */ + color: var(--color-text-secondary); } .zap-split-label { font-weight: 500; } .zap-split-slider { width: 100%; height: 8px; border-radius: 4px; - background: rgb(39 39 42); /* zinc-800 */ + background: var(--color-bg-elevated); outline: none; -webkit-appearance: none; } @@ -64,36 +64,36 @@ width: 20px; height: 20px; border-radius: 50%; - background: rgb(99 102 241); /* indigo-500 */ + background: var(--color-primary); cursor: pointer; transition: all 0.2s ease; } .zap-split-slider::-webkit-slider-thumb:hover { - background: rgb(79 70 229); /* indigo-600 */ + background: var(--color-primary-hover); transform: scale(1.1); } .zap-split-slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; - background: rgb(99 102 241); /* indigo-500 */ + background: var(--color-primary); cursor: pointer; border: none; transition: all 0.2s ease; } .zap-split-slider::-moz-range-thumb:hover { - background: rgb(79 70 229); /* indigo-600 */ + background: var(--color-primary-hover); transform: scale(1.1); } .zap-split-description { margin-top: 1.5rem; padding: 1rem; - background: rgb(39 39 42); /* zinc-800 */ - border: 1px solid rgb(63 63 70); /* zinc-700 */ + background: var(--color-bg-elevated); + border: 1px solid var(--color-border); border-radius: 6px; font-size: 0.875rem; line-height: 1.5; - color: rgb(161 161 170); /* zinc-400 */ + color: var(--color-text-secondary); } /* Relay items */ @@ -146,4 +146,3 @@ } } - diff --git a/src/styles/layout/highlights.css b/src/styles/layout/highlights.css index 52ebd8b4..5fb2b3e4 100644 --- a/src/styles/layout/highlights.css +++ b/src/styles/layout/highlights.css @@ -1,6 +1,6 @@ /* Highlights panel layout and interactions */ .highlights-container { - background: rgb(24 24 27); /* zinc-900 */ + background: var(--color-bg); display: flex; flex-direction: column; height: 100%; @@ -25,8 +25,8 @@ } .highlights-container.collapsed .toggle-highlights-btn { - background: rgb(39 39 42); /* zinc-800 */ - color: rgb(228 228 231); /* zinc-200 */ + background: var(--color-bg-elevated); + color: var(--color-text); border: none; padding: 0; border-radius: 0; @@ -39,7 +39,7 @@ height: 36px; } -.highlights-container.collapsed .toggle-highlights-btn:hover { background: rgb(63 63 70); /* zinc-700 */ color: rgb(255 255 255); /* white */ } +.highlights-container.collapsed .toggle-highlights-btn:hover { background: var(--color-border); color: var(--color-text); } .highlights-container.collapsed .toggle-highlights-btn:active { transform: translateY(1px); } .highlights-container.collapsed .toggle-highlights-btn.with-icon { width: auto; padding: 0 0.5rem; gap: 0.5rem; } @@ -48,8 +48,8 @@ align-items: center; justify-content: space-between; padding: 0.75rem 1rem; - border-bottom: 1px solid rgb(63 63 70); /* zinc-700 */ - background: rgb(24 24 27); /* zinc-900 */ + border-bottom: 1px solid var(--color-border); + background: var(--color-bg); border-radius: 12px 12px 0 0; } @@ -65,32 +65,32 @@ .highlights-title { display: flex; align-items: center; gap: 0.5rem; } .highlights-title h3 { margin: 0; font-size: 1rem; font-weight: 600; } -.highlights-title .count { color: rgb(161 161 170); /* zinc-400 */ font-size: 0.875rem; } +.highlights-title .count { color: var(--color-text-secondary); font-size: 0.875rem; } .highlight-mode-toggle { display: flex; gap: 0.25rem; padding: 0.25rem; background: rgba(255, 255, 255, 0.05); border-radius: 4px; } -.highlight-mode-toggle .mode-btn { background: none; border: none; color: rgb(161 161 170); /* zinc-400 */ cursor: pointer; padding: 0.375rem 0.5rem; border-radius: 3px; transition: all 0.2s; font-size: 0.9rem; } -.highlight-mode-toggle .mode-btn:hover { background: rgba(255, 255, 255, 0.1); color: rgb(255 255 255); /* white */ } -.highlight-mode-toggle .mode-btn.active { background: rgb(99 102 241); /* indigo-500 */ color: rgb(255 255 255); /* white */ } +.highlight-mode-toggle .mode-btn { background: none; border: none; color: var(--color-text-secondary); cursor: pointer; padding: 0.375rem 0.5rem; border-radius: 3px; transition: all 0.2s; font-size: 0.9rem; } +.highlight-mode-toggle .mode-btn:hover { background: rgba(255, 255, 255, 0.1); color: var(--color-text); } +.highlight-mode-toggle .mode-btn.active { background: var(--color-primary); color: rgb(255 255 255); /* white */ } /* Three-level highlight toggles */ .highlight-level-toggles { display: flex; gap: 0.25rem; padding: 0.25rem; background: rgba(255, 255, 255, 0.05); border-radius: 4px; } .highlights-loading, -.highlights-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; color: rgb(161 161 170); /* zinc-400 */ text-align: center; gap: 0.5rem; } -.highlights-empty svg { color: rgb(113 113 122); /* zinc-500 */ margin-bottom: 0.5rem; } -.empty-hint { font-size: 0.875rem; color: rgb(113 113 122); /* zinc-500 */ margin-top: 0.5rem; } +.highlights-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 2rem 1rem; color: var(--color-text-secondary); text-align: center; gap: 0.5rem; } +.highlights-empty svg { color: var(--color-text-muted); margin-bottom: 0.5rem; } +.empty-hint { font-size: 0.875rem; color: var(--color-text-muted); margin-top: 0.5rem; } .highlights-list { overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; } -.highlight-item { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 8px; padding: 0; display: flex; transition: border-color 0.2s ease; position: relative; } -.highlight-item:hover { border-color: rgb(99 102 241); /* indigo-500 */ } -.highlight-item.selected { border-color: rgb(99 102 241); /* indigo-500 */ background: rgb(39 39 42); /* zinc-800 */ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); } +.highlight-item { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 0; display: flex; transition: border-color 0.2s ease; position: relative; } +.highlight-item:hover { border-color: var(--color-primary); } +.highlight-item.selected { border-color: var(--color-primary); background: var(--color-bg-elevated); box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); } /* Compact button for highlight cards */ -.compact-button { background: none; border: none; color: rgb(161 161 170); /* zinc-400 */ cursor: pointer; padding: 0.25rem; font-size: 0.75rem; display: flex; align-items: center; justify-content: center; gap: 0.25rem; transition: all 0.2s ease; border-radius: 4px; min-width: 20px; min-height: 20px; } -.compact-button:hover { color: rgb(212 212 216); /* zinc-300 */ background: rgba(255, 255, 255, 0.05); } +.compact-button { background: none; border: none; color: var(--color-text-secondary); cursor: pointer; padding: 0.25rem; font-size: 0.75rem; display: flex; align-items: center; justify-content: center; gap: 0.25rem; transition: all 0.2s ease; border-radius: 4px; min-width: 20px; min-height: 20px; } +.compact-button:hover { color: var(--color-text); background: rgba(255, 255, 255, 0.05); } .compact-button:active { transform: scale(0.95); } .compact-button:disabled { opacity: 0.5; cursor: not-allowed; } -.compact-button:disabled:hover { background: none; color: rgb(161 161 170); /* zinc-400 */ transform: none; } +.compact-button:disabled:hover { background: none; color: var(--color-text-secondary); transform: none; } .highlight-header { position: absolute; top: 0; left: 0; right: 0; padding: 0.25rem 0.5rem; display: flex; align-items: center; justify-content: flex-end; pointer-events: none; border-top-left-radius: 8px; border-top-right-radius: 8px; transition: border-color 0.2s ease; } .highlight-header .compact-button { pointer-events: auto; } @@ -127,17 +127,17 @@ .highlight-item.level-nostrverse .highlight-quote-icon { color: var(--highlight-color-nostrverse, #9333ea); } .highlight-content { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 2.25rem 0.75rem; } -.highlight-text { margin: 0; padding: 0; font-style: italic; color: rgb(228 228 231); /* zinc-200 */ line-height: 1.6; border-left: none; font-size: 0.95rem; } -.highlight-comment { margin-top: 0.5rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: rgb(228 228 231); /* zinc-200 */ line-height: 1.5; } +.highlight-text { margin: 0; padding: 0; font-style: italic; color: var(--color-text); line-height: 1.6; border-left: none; font-size: 0.95rem; } +.highlight-comment { margin-top: 0.5rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: var(--color-text); line-height: 1.5; } /* Level-colored comments */ .highlight-item.level-mine .highlight-comment { background: color-mix(in srgb, var(--highlight-color-mine, #ffff00) 10%, transparent); border-left-color: var(--highlight-color-mine, #ffff00); } .highlight-item.level-friends .highlight-comment { background: color-mix(in srgb, var(--highlight-color-friends, #f97316) 10%, transparent); border-left-color: var(--highlight-color-friends, #f97316); } .highlight-item.level-nostrverse .highlight-comment { background: color-mix(in srgb, var(--highlight-color-nostrverse, #9333ea) 10%, transparent); border-left-color: var(--highlight-color-nostrverse, #9333ea); } -.highlight-footer { position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: space-between; padding: 0.25rem 0.5rem; font-size: 0.8rem; color: rgb(161 161 170); /* zinc-400 */ border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; transition: border-color 0.2s ease; } +.highlight-footer { position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: center; justify-content: space-between; padding: 0.25rem 0.5rem; font-size: 0.8rem; color: var(--color-text-secondary); border-bottom-left-radius: 8px; border-bottom-right-radius: 8px; transition: border-color 0.2s ease; } .highlight-footer-left { display: flex; align-items: center; gap: 0.4rem; min-width: 0; } -.highlight-author { color: rgb(212 212 216); /* zinc-300 */ font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; display: inline-flex; align-items: center; min-height: 28px; } +.highlight-author { color: var(--color-text); font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; display: inline-flex; align-items: center; min-height: 28px; } /* Ensure relay indicator in footer uses normal flow and matches CompactButton spacing */ .highlight-item .highlight-footer .highlight-relay-indicator { @@ -148,11 +148,10 @@ padding: 0.25rem; /* CompactButton base */ } .highlight-menu-wrapper { position: relative; flex-shrink: 0; display: flex; align-items: center; } -.highlight-menu { position: absolute; right: 0; top: calc(100% + 4px); background: rgb(39 39 42); /* zinc-800 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 1000; min-width: 160px; overflow: hidden; } -.highlight-menu-item { width: 100%; background: none; border: none; color: rgb(228 228 231); /* zinc-200 */ padding: 0.625rem 0.875rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.625rem; cursor: pointer; transition: all 0.15s ease; text-align: left; white-space: nowrap; } -.highlight-menu-item:hover { background: rgba(99, 102, 241, 0.15); color: rgb(255 255 255); /* white */ } +.highlight-menu { position: absolute; right: 0; top: calc(100% + 4px); background: var(--color-bg-elevated); border: 1px solid var(--color-border-subtle); border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 1000; min-width: 160px; overflow: hidden; } +.highlight-menu-item { width: 100%; background: none; border: none; color: var(--color-text); padding: 0.625rem 0.875rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.625rem; cursor: pointer; transition: all 0.15s ease; text-align: left; white-space: nowrap; } +.highlight-menu-item:hover { background: rgba(99, 102, 241, 0.15); color: var(--color-text); } .highlight-menu-item:disabled { opacity: 0.5; cursor: not-allowed; } .highlight-menu-item-danger:hover { background: rgba(255, 68, 68, 0.15); color: rgb(239 68 68); /* red-500 */ } .highlight-menu-item svg { font-size: 0.875rem; flex-shrink: 0; } - diff --git a/src/styles/layout/sidebar.css b/src/styles/layout/sidebar.css index 97f2c181..140415ee 100644 --- a/src/styles/layout/sidebar.css +++ b/src/styles/layout/sidebar.css @@ -1,6 +1,6 @@ /* Bookmarks and sidebar layout */ .bookmarks-container { - background: rgb(24 24 27); /* zinc-900 */ + background: var(--color-bg); display: flex; flex-direction: column; height: 100%; @@ -20,7 +20,7 @@ .bookmarks-container .view-mode-controls { margin-top: auto; padding: 1rem; - border-top: 1px solid rgb(63 63 70); /* zinc-700 */ + border-top: 1px solid var(--color-border); background: transparent; border-radius: 0; } @@ -41,15 +41,15 @@ justify-content: space-between; gap: 0.75rem; padding: 0.75rem 1rem; - background: rgb(24 24 27); /* zinc-900 */ - border-bottom: 1px solid rgb(63 63 70); /* zinc-700 */ + background: var(--color-bg); + border-bottom: 1px solid var(--color-border); margin-bottom: 0; } /* Mobile: add borders and rounded corners */ @media (max-width: 768px) { .sidebar-header-bar { - border: 1px solid rgb(63 63 70); /* zinc-700 */ + border: 1px solid var(--color-border); border-radius: 12px 12px 0 0; } } @@ -95,10 +95,10 @@ display: flex; align-items: center; justify-content: center; - background: rgb(39 39 42); /* zinc-800 */ - border: 1px solid rgb(82 82 91); /* zinc-600 */ + background: var(--color-bg-elevated); + border: 1px solid var(--color-border-subtle); flex-shrink: 0; - color: rgb(228 228 231); /* zinc-200 */ + color: var(--color-text); box-sizing: border-box; padding: 0; } @@ -108,8 +108,8 @@ .sidebar-header-bar .toggle-sidebar-btn { background: transparent; - color: rgb(228 228 231); /* zinc-200 */ - border: 1px solid rgb(82 82 91); /* zinc-600 */ + color: var(--color-text); + border: 1px solid var(--color-border-subtle); padding: 0; border-radius: 6px; cursor: pointer; @@ -123,7 +123,7 @@ box-sizing: border-box; } -.sidebar-header-bar .toggle-sidebar-btn:hover { background: rgb(39 39 42); /* zinc-800 */ color: rgb(255 255 255); /* white */ } +.sidebar-header-bar .toggle-sidebar-btn:hover { background: var(--color-bg-elevated); color: var(--color-text); } .sidebar-header-bar .toggle-sidebar-btn:active { transform: translateY(1px); } .bookmarks-container.collapsed { @@ -136,8 +136,8 @@ } .bookmarks-container.collapsed .toggle-sidebar-btn { - background: rgb(39 39 42); /* zinc-800 */ - color: rgb(228 228 231); /* zinc-200 */ + background: var(--color-bg-elevated); + color: var(--color-text); border: none; padding: 0; border-radius: 0; @@ -151,22 +151,21 @@ flex-shrink: 0; } -.bookmarks-container.collapsed .toggle-sidebar-btn:hover { background: rgb(63 63 70); /* zinc-700 */ color: rgb(255 255 255); /* white */ } +.bookmarks-container.collapsed .toggle-sidebar-btn:hover { background: var(--color-border); color: var(--color-text); } .bookmarks-container.collapsed .toggle-sidebar-btn:active { transform: translateY(1px); } .bookmarks-container.collapsed .toggle-sidebar-btn.with-icon { width: auto; padding: 0 0.5rem; gap: 0.5rem; } -.bookmarks-container.collapsed .toggle-sidebar-btn .glow-blue { color: rgb(99 102 241); /* indigo-500 */ filter: drop-shadow(0 0 4px rgba(99, 102, 241, 0.6)); } +.bookmarks-container.collapsed .toggle-sidebar-btn .glow-blue { color: var(--color-primary); filter: drop-shadow(0 0 4px rgba(99, 102, 241, 0.6)); } -.user-info { margin: 0.5rem 0 0 0; color: rgb(161 161 170); /* zinc-400 */ font-size: 0.9rem; font-family: monospace; } -.bookmark-count { color: rgb(113 113 122); /* zinc-500 */ font-size: 0.9rem; margin: 0.5rem 0; } -.event-link { color: rgb(96 165 250); /* blue-400 */ text-decoration: none; font-weight: 500; } +.user-info { margin: 0.5rem 0 0 0; color: var(--color-text-secondary); font-size: 0.9rem; font-family: monospace; } +.bookmark-count { color: var(--color-text-muted); font-size: 0.9rem; margin: 0.5rem 0; } +.event-link { color: var(--color-primary); text-decoration: none; font-weight: 500; } .event-link:hover { text-decoration: underline; } .bookmark-urls { margin: 0.75rem 0; } -.bookmark-url { display: block; margin: 0.25rem 0; color: rgb(59 130 246); /* blue-500 */ text-decoration: none; word-break: break-all; background: none; border: none; padding: 0; font: inherit; cursor: pointer; text-align: left; width: 100%; } +.bookmark-url { display: block; margin: 0.25rem 0; color: var(--color-primary); text-decoration: none; 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; } .url-row { display: flex; align-items: center; gap: 0.5rem; } .read-inline-btn { background: rgb(34 197 94); /* green-500 */ color: white; border: none; padding: 0.25rem 0.5rem; border-radius: 4px; cursor: pointer; } .read-inline-btn:hover { background: rgb(22 163 74); /* green-600 */ } - From 9251b017d4f8bac406180ccc2944fe9d6a339c7e Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:24:31 +0200 Subject: [PATCH 03/23] refactor(css): migrate remaining components to semantic tokens - Update icon-button.css, profile.css, me.css to use tokens - Migrate reader.css to semantic colors for light theme - Update toast.css with theme-aware colors - All major UI components now support theme switching --- src/styles/components/icon-button.css | 21 +++++---- src/styles/components/me.css | 34 +++++++------- src/styles/components/profile.css | 11 +++-- src/styles/components/reader.css | 65 +++++++++++++-------------- src/styles/components/toast.css | 3 +- 5 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/styles/components/icon-button.css b/src/styles/components/icon-button.css index 5090fa0a..464c1c82 100644 --- a/src/styles/components/icon-button.css +++ b/src/styles/components/icon-button.css @@ -3,10 +3,10 @@ display: inline-flex; align-items: center; justify-content: center; - border: 1px solid rgb(82 82 91); /* zinc-600 */ + border: 1px solid var(--color-border-subtle); border-radius: 6px; - background: rgb(39 39 42); /* zinc-800 */ - color: rgb(228 228 231); /* zinc-200 */ + background: var(--color-bg-elevated); + color: var(--color-text); cursor: pointer; min-width: 33px; min-height: 33px; @@ -14,16 +14,16 @@ box-sizing: border-box; } -.icon-button:hover { background: rgb(63 63 70); /* zinc-700 */ } +.icon-button:hover { background: var(--color-border); } .icon-button:active { transform: translateY(1px); } -.icon-button.primary { background: rgb(99 102 241); /* indigo-500 */ color: white; border-color: rgb(99 102 241); /* indigo-500 */ } +.icon-button.primary { background: var(--color-primary); color: white; border-color: var(--color-primary); } .icon-button.primary:hover { filter: brightness(1.05); } -.icon-button.success { background: rgb(99 102 241); /* indigo-500 */ color: white; border-color: rgb(99 102 241); /* indigo-500 */ } +.icon-button.success { background: var(--color-primary); color: white; border-color: var(--color-primary); } .icon-button.success:hover { filter: brightness(1.1); } -.icon-button.ghost { background: rgb(39 39 42); /* zinc-800 */ } +.icon-button.ghost { background: var(--color-bg-elevated); } /* Mobile touch target improvements */ @media (max-width: 768px) { @@ -42,9 +42,8 @@ /* Disable hover effects on touch devices */ @media (pointer: coarse) { - .icon-button:hover { background: rgb(39 39 42); /* zinc-800 */ } - .icon-button.ghost:hover { background: rgb(39 39 42); /* zinc-800 */ } - .icon-button:active { background: rgb(63 63 70); /* zinc-700 */ } + .icon-button:hover { background: var(--color-bg-elevated); } + .icon-button.ghost:hover { background: var(--color-bg-elevated); } + .icon-button:active { background: var(--color-border); } } - diff --git a/src/styles/components/me.css b/src/styles/components/me.css index 00b8edc0..a235be33 100644 --- a/src/styles/components/me.css +++ b/src/styles/components/me.css @@ -3,7 +3,7 @@ display: flex; gap: 0.5rem; margin-top: 1rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: 1px solid var(--color-border); overflow-x: auto; max-width: 600px; margin-left: auto; @@ -18,7 +18,7 @@ background: none; border: none; border-bottom: 2px solid transparent; - color: var(--text-secondary, rgb(161 161 170)); /* zinc-400 */ + color: var(--color-text-secondary); font-size: 0.95rem; font-weight: 500; cursor: pointer; @@ -28,13 +28,13 @@ } .me-tab:hover { - color: var(--text-primary, rgb(228 228 231)); /* zinc-200 */ - background: rgba(255, 255, 255, 0.05); + color: var(--color-text); + background: var(--color-bg-elevated); } .me-tab.active { - color: var(--primary-color, rgb(139 92 246)); /* purple-500 */ - border-bottom-color: var(--primary-color, rgb(139 92 246)); /* purple-500 */ + color: var(--color-primary); + border-bottom-color: var(--color-primary); } /* Highlights tab uses the user's custom "my highlights" color */ @@ -45,8 +45,8 @@ /* Reading List tab uses blue color to match bookmarks icon */ .me-tab[data-tab="reading-list"].active { - color: rgb(99 102 241); /* indigo-500 */ - border-bottom-color: rgb(99 102 241); /* indigo-500 */ + color: var(--color-primary); + border-bottom-color: var(--color-primary); } .me-tab svg { @@ -80,25 +80,25 @@ /* Enhanced border styling for reading list cards */ .bookmarks-list .individual-bookmark { - border: 1px solid rgb(82 82 91) !important; /* zinc-600 */ - background: rgb(24 24 27) !important; /* zinc-900 */ + border: 1px solid var(--color-border-subtle) !important; + background: var(--color-bg) !important; } .bookmarks-list .individual-bookmark:hover { - border-color: rgb(113 113 122) !important; /* zinc-500 */ - background: rgb(39 39 42) !important; /* zinc-800 */ + border-color: var(--color-border) !important; + background: var(--color-bg-elevated) !important; } .bookmark-item { padding: 1rem; - background: var(--card-bg, rgb(255 255 255)); /* white */ - border: 1px solid var(--border-color, rgb(228 228 231)); /* zinc-200 */ + background: var(--color-bg); + border: 1px solid var(--color-border); border-radius: 8px; transition: all 0.2s ease; } .bookmark-item:hover { - border-color: var(--primary-color, rgb(139 92 246)); /* purple-500 */ + border-color: var(--color-primary); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } @@ -110,13 +110,13 @@ .bookmark-item h3 { margin: 0 0 0.5rem 0; font-size: 1.1rem; - color: var(--text-primary, rgb(0 0 0)); /* black */ + color: var(--color-text); } .bookmark-item p { margin: 0; font-size: 0.9rem; - color: var(--text-secondary, rgb(113 113 122)); /* zinc-500 */ + color: var(--color-text-secondary); line-height: 1.5; } diff --git a/src/styles/components/profile.css b/src/styles/components/profile.css index aa620061..caad4eaa 100644 --- a/src/styles/components/profile.css +++ b/src/styles/components/profile.css @@ -1,14 +1,14 @@ /* Profile UI fragments */ .author-card-container { display: flex; justify-content: center; padding: 2rem 1rem; } -.author-card { display: flex; gap: 1rem; padding: 1.5rem; background: rgb(24 24 27); /* zinc-900 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 12px; max-width: 600px; width: 100%; transition: all 0.2s ease; } -.author-card-clickable:hover { border-color: rgb(99 102 241); /* indigo-500 */ background: rgb(30 30 33); /* slightly lighter */ transform: translateY(-1px); } +.author-card { display: flex; gap: 1rem; padding: 1.5rem; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: 12px; max-width: 600px; width: 100%; transition: all 0.2s ease; } +.author-card-clickable:hover { border-color: var(--color-primary); background: var(--color-bg-elevated); transform: translateY(-1px); } .author-card-clickable:active { transform: translateY(0); } -.author-card-avatar { flex-shrink: 0; width: 60px; height: 60px; border-radius: 50%; overflow: hidden; background: rgb(39 39 42); /* zinc-800 */ display: flex; align-items: center; justify-content: center; color: rgb(113 113 122); /* zinc-500 */ } +.author-card-avatar { flex-shrink: 0; width: 60px; height: 60px; border-radius: 50%; overflow: hidden; background: var(--color-bg-elevated); display: flex; align-items: center; justify-content: center; color: var(--color-text-muted); } .author-card-avatar img { width: 100%; height: 100%; object-fit: cover; } .author-card-avatar svg { font-size: 2.5rem; } .author-card-content { flex: 1; min-width: 0; text-align: left; } -.author-card-name { font-size: 1rem; font-weight: 600; color: rgb(228 228 231); /* zinc-200 */ margin-bottom: 0.5rem; text-align: left; } -.author-card-bio { font-size: 0.9rem; color: rgb(161 161 170); /* zinc-400 */ line-height: 1.5; margin: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; text-align: left; } +.author-card-name { font-size: 1rem; font-weight: 600; color: var(--color-text); margin-bottom: 0.5rem; text-align: left; } +.author-card-bio { font-size: 0.9rem; color: var(--color-text-secondary); line-height: 1.5; margin: 0; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; text-align: left; } @media (max-width: 768px) { .author-card-container { @@ -28,4 +28,3 @@ .author-card-bio { font-size: 0.85rem; -webkit-line-clamp: 2; } } - diff --git a/src/styles/components/reader.css b/src/styles/components/reader.css index 4533de47..dd9d3bdf 100644 --- a/src/styles/components/reader.css +++ b/src/styles/components/reader.css @@ -1,7 +1,7 @@ /* Reader view */ .reader { - 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; padding: 0.75rem; text-align: left; @@ -21,22 +21,22 @@ margin: 0 -0.75rem 1rem -0.75rem; /* Negative margins to counteract reader padding */ background: rgb(0 0 0); /* black */ } -.reader.empty { color: rgb(161 161 170); /* zinc-400 */ } -.loading-spinner { display: flex; align-items: center; gap: 0.5rem; color: rgb(161 161 170); /* zinc-400 */ } +.reader.empty { color: var(--color-text-secondary); } +.loading-spinner { display: flex; align-items: center; gap: 0.5rem; color: var(--color-text-secondary); } .loading-spinner svg { font-size: 1.2rem; } .reader-header { margin-bottom: 2rem; position: relative; } .reader-title { margin: 0 0 0.75rem 0; font-family: var(--reading-font); font-size: 2.5rem; font-weight: 700; line-height: 1.2; } -.reader-summary { color: rgb(212 212 216); /* zinc-300 */ font-size: 1.2rem; line-height: 1.6; margin: 0 0 1rem 0; font-family: var(--reading-font); } +.reader-summary { color: var(--color-text); font-size: 1.2rem; line-height: 1.6; margin: 0 0 1rem 0; font-family: var(--reading-font); } .reader-meta { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; } -.publish-date { display: flex; align-items: center; gap: 0.4rem; font-size: 0.813rem; color: rgba(161, 161, 170, 0.7); /* zinc-400 */ opacity: 0.85; } +.publish-date { display: flex; align-items: center; gap: 0.4rem; font-size: 0.813rem; color: var(--color-text-muted); opacity: 0.85; } .publish-date svg { font-size: 0.75rem; opacity: 0.6; } -.publish-date-topright { position: absolute; top: 1rem; right: 1rem; font-size: 0.813rem; color: rgb(255 255 255); /* white */ padding: 0.4rem 0.75rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); z-index: 10; } -.reading-time { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; background: rgba(161, 161, 170, 0.1); /* zinc-400 */ border: 1px solid rgba(161, 161, 170, 0.3); /* zinc-400 */ border-radius: 6px; font-size: 0.875rem; color: rgb(161 161 170); /* zinc-400 */ } +.publish-date-topright { position: absolute; top: 1rem; right: 1rem; font-size: 0.813rem; color: var(--color-text); padding: 0.4rem 0.75rem; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); z-index: 10; } +.reading-time { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; background: var(--color-bg-elevated); border: 1px solid var(--color-border); border-radius: 6px; font-size: 0.875rem; color: var(--color-text-secondary); } .reading-time svg { font-size: 0.875rem; } -.highlight-indicator { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; background: rgba(99, 102, 241, 0.1); /* indigo-500 */ border: 1px solid rgba(99, 102, 241, 0.3); /* indigo-500 */ border-radius: 6px; font-size: 0.875rem; color: rgb(99 102 241); /* indigo-500 */ } +.highlight-indicator { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; background: rgba(99, 102, 241, 0.1); border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 6px; font-size: 0.875rem; color: var(--color-primary); } .highlight-indicator svg { font-size: 0.875rem; } -.reader-html { color: rgb(228 228 231); /* zinc-200 */ line-height: 1.6; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; font-family: var(--reading-font); font-size: var(--reading-font-size); } -.reader-markdown { color: rgb(228 228 231); /* zinc-200 */ line-height: 1.7; font-family: var(--reading-font); font-size: var(--reading-font-size); } +.reader-html { color: var(--color-text); line-height: 1.6; word-wrap: break-word; overflow-wrap: break-word; word-break: break-word; font-family: var(--reading-font); font-size: var(--reading-font-size); } +.reader-markdown { color: var(--color-text); line-height: 1.7; font-family: var(--reading-font); font-size: var(--reading-font-size); } /* Ensure content is left-aligned even if source markup uses center */ .reader .reader-html *, .reader .reader-markdown * { text-align: left !important; font-family: inherit !important; } .reader center, .reader [align="center"] { text-align: left !important; } @@ -49,7 +49,7 @@ line-height: 1.2; margin-top: 2rem; margin-bottom: 1rem; - color: rgb(244 244 245); /* zinc-100 */ + color: var(--color-text); } .reader-markdown h2, .reader-html h2 { font-size: 1.875rem; /* text-3xl */ @@ -57,7 +57,7 @@ line-height: 1.3; margin-top: 1.75rem; margin-bottom: 0.875rem; - color: rgb(244 244 245); /* zinc-100 */ + color: var(--color-text); } .reader-markdown h3, .reader-html h3 { font-size: 1.5rem; /* text-2xl */ @@ -65,7 +65,7 @@ line-height: 1.4; margin-top: 1.5rem; margin-bottom: 0.75rem; - color: rgb(244 244 245); /* zinc-100 */ + color: var(--color-text); } .reader-markdown h4, .reader-html h4 { font-size: 1.25rem; /* text-xl */ @@ -73,7 +73,7 @@ line-height: 1.4; margin-top: 1.25rem; margin-bottom: 0.625rem; - color: rgb(228 228 231); /* zinc-200 */ + color: var(--color-text); } .reader-markdown h5, .reader-html h5 { font-size: 1.125rem; /* text-lg */ @@ -81,7 +81,7 @@ line-height: 1.4; margin-top: 1rem; margin-bottom: 0.5rem; - color: rgb(228 228 231); /* zinc-200 */ + color: var(--color-text); } .reader-markdown h6, .reader-html h6 { font-size: 1rem; /* text-base */ @@ -89,7 +89,7 @@ line-height: 1.4; margin-top: 1rem; margin-bottom: 0.5rem; - color: rgb(228 228 231); /* zinc-200 */ + color: var(--color-text); } .reader-markdown p { margin: 0.5rem 0; } .reader-html p, .reader-html div, .reader-html span, .reader-html li, .reader-html td, .reader-html th { font-size: 1em !important; } @@ -107,7 +107,7 @@ .reader-markdown li, .reader-html li { margin: 0.375rem 0; line-height: 1.6; - color: rgb(228 228 231); /* zinc-200 */ + color: var(--color-text); } .reader-markdown ul ul, .reader-markdown ol ul, .reader-html ul ul, .reader-html ol ul { list-style-type: circle; @@ -128,16 +128,16 @@ .reader-markdown blockquote p, .reader-html blockquote p { margin: 0.5rem 0; } .reader-markdown blockquote p:first-child, .reader-html blockquote p:first-child { margin-top: 0; } .reader-markdown blockquote p:last-child, .reader-html blockquote p:last-child { margin-bottom: 0; } -.reader-markdown a { color: rgb(96 165 250); /* blue-400 */ text-decoration: none; } +.reader-markdown a { color: var(--color-primary); text-decoration: none; } .reader-markdown a:hover { text-decoration: underline; } -.reader-markdown code { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.9em; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } -.reader-markdown pre { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; line-height: 1.5; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } +.reader-markdown code { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.9em; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } +.reader-markdown pre { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; line-height: 1.5; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } .reader-markdown pre code { background: transparent; border: none; padding: 0; font-size: 0.9em; display: block; } /* Prism.js enhancements */ -.reader-markdown pre[class*="language-"] { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ } +.reader-markdown pre[class*="language-"] { background: var(--color-bg-subtle); border: 1px solid var(--color-border); } .reader-markdown code[class*="language-"] { background: transparent; text-shadow: none; } -.reader-html pre { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } -.reader-html code { background: rgb(30 30 30); /* ~zinc-850 */ border: 1px solid rgb(63 63 70); /* zinc-700 */ border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.9em; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } +.reader-html pre { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 1rem; overflow-x: auto; margin: 1rem 0; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } +.reader-html code { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 4px; padding: 0.15rem 0.4rem; font-size: 0.9em; font-family: 'Monaco', 'Menlo', 'Consolas', 'Courier New', monospace; } .reader-html pre code { background: transparent; border: none; padding: 0; display: block; } /* Mobile: prevent code blocks from causing horizontal overflow */ @media (max-width: 768px) { @@ -182,17 +182,17 @@ /* Article menu */ .article-menu-container { display: flex; justify-content: flex-end; padding: 1.5rem 0 0.5rem; margin-top: 2rem; } .article-menu-wrapper { position: relative; } -.article-menu-btn { background: none; border: none; color: rgb(161 161 170); /* zinc-400 */ cursor: pointer; padding: 0.5rem 0.75rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s ease; border-radius: 6px; } -.article-menu-btn:hover { color: rgb(99 102 241); /* indigo-500 */ background: rgba(99, 102, 241, 0.1); } -.article-menu { position: absolute; right: 0; top: calc(100% + 4px); background: rgb(39 39 42); /* zinc-800 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 1000; min-width: 180px; overflow: hidden; } -.article-menu-item { width: 100%; background: none; border: none; color: rgb(228 228 231); /* zinc-200 */ padding: 0.75rem 1rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.75rem; cursor: pointer; transition: all 0.15s ease; text-align: left; white-space: nowrap; } -.article-menu-item:hover { background: rgba(99, 102, 241, 0.15); color: rgb(255 255 255); /* white */ } +.article-menu-btn { background: none; border: none; color: var(--color-text-secondary); cursor: pointer; padding: 0.5rem 0.75rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.5rem; transition: all 0.2s ease; border-radius: 6px; } +.article-menu-btn:hover { color: var(--color-primary); background: rgba(99, 102, 241, 0.1); } +.article-menu { position: absolute; right: 0; top: calc(100% + 4px); background: var(--color-bg-elevated); border: 1px solid var(--color-border-subtle); border-radius: 6px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 1000; min-width: 180px; overflow: hidden; } +.article-menu-item { width: 100%; background: none; border: none; color: var(--color-text); padding: 0.75rem 1rem; font-size: 0.875rem; display: flex; align-items: center; gap: 0.75rem; cursor: pointer; transition: all 0.15s ease; text-align: left; white-space: nowrap; } +.article-menu-item:hover { background: rgba(99, 102, 241, 0.15); color: var(--color-text); } .article-menu-item svg { font-size: 0.875rem; flex-shrink: 0; } /* Mark as Read button */ .mark-as-read-container { display: flex; justify-content: center; align-items: center; padding: 2rem 1rem; margin-top: 1rem; } -.mark-as-read-btn { display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.5rem; background: rgb(39 39 42); /* zinc-800 */ color: rgb(228 228 231); /* zinc-200 */ border: 1px solid rgb(82 82 91); /* zinc-600 */ border-radius: 8px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; min-width: 160px; justify-content: center; } -.mark-as-read-btn:hover:not(:disabled) { background: rgb(63 63 70); /* zinc-700 */ border-color: rgb(113 113 122); /* zinc-500 */ transform: translateY(-1px); } +.mark-as-read-btn { display: flex; align-items: center; gap: 0.5rem; padding: 0.75rem 1.5rem; background: var(--color-bg-elevated); color: var(--color-text); border: 1px solid var(--color-border-subtle); border-radius: 8px; font-size: 1rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; min-width: 160px; justify-content: center; } +.mark-as-read-btn:hover:not(:disabled) { background: var(--color-border); border-color: var(--color-text-muted); transform: translateY(-1px); } .mark-as-read-btn:active:not(:disabled) { transform: translateY(0); } .mark-as-read-btn:disabled { opacity: 0.6; cursor: not-allowed; } .mark-as-read-btn svg { font-size: 1.1rem; } @@ -228,7 +228,7 @@ @media (max-width: 768px) { .reader-header-overlay .reader-summary.hide-on-mobile { display: none; } .reader-summary-below-image { display: block; padding: 0 0 1.5rem 0; margin-top: -1rem; } - .reader-summary-below-image .reader-summary { color: #aaa; font-size: 1rem; line-height: 1.6; margin: 0; } + .reader-summary-below-image .reader-summary { color: var(--color-text-secondary); font-size: 1rem; line-height: 1.6; margin: 0; } .reader-hero-image { min-height: 280px; max-height: 400px; height: 50vh; } .reader-hero-image img { height: 100%; width: 100%; object-fit: cover; object-position: center; } .reader-header-overlay { padding: 1.5rem 1rem 1rem; } @@ -237,4 +237,3 @@ /* Reading Progress Indicator - now using Tailwind utilities in component */ - diff --git a/src/styles/components/toast.css b/src/styles/components/toast.css index 8c946bac..0766bf32 100644 --- a/src/styles/components/toast.css +++ b/src/styles/components/toast.css @@ -1,5 +1,5 @@ /* Toast Notification */ -.toast { position: fixed; top: 2rem; right: 2rem; background: rgb(24 24 27); /* zinc-900 */ color: rgb(255 255 255); /* white */ padding: 1rem 1.5rem; border-radius: 8px; border: 1px solid rgb(63 63 70); /* zinc-700 */ display: flex; align-items: center; gap: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); animation: toast-slide-in 0.3s ease-out; z-index: 9999; font-size: 0.95rem; } +.toast { position: fixed; top: 2rem; right: 2rem; background: var(--color-bg); color: var(--color-text); padding: 1rem 1.5rem; border-radius: 8px; border: 1px solid var(--color-border); display: flex; align-items: center; gap: 0.75rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); animation: toast-slide-in 0.3s ease-out; z-index: 9999; font-size: 0.95rem; } @media (max-width: 768px) { .toast { top: auto; bottom: calc(1rem + var(--safe-area-bottom)); right: 1rem; left: 1rem; max-width: calc(100% - 2rem); } @keyframes toast-slide-in { from { transform: translateY(100px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @@ -10,4 +10,3 @@ .toast-error svg { color: rgb(220 38 38); /* red-600 */ } @keyframes toast-slide-in { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } - From ba8229d46485d7a260f68eea065f1e2bb8fb8c3d Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:24:49 +0200 Subject: [PATCH 04/23] refactor(css): update pull-to-refresh to use semantic tokens --- src/styles/components/pull-to-refresh.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/styles/components/pull-to-refresh.css b/src/styles/components/pull-to-refresh.css index f181f85f..da978c2e 100644 --- a/src/styles/components/pull-to-refresh.css +++ b/src/styles/components/pull-to-refresh.css @@ -21,21 +21,21 @@ display: flex; align-items: center; justify-content: center; - background: var(--background-secondary); + background: var(--color-bg-elevated); border-radius: 50%; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; font-size: 1rem; - color: var(--text-secondary); + color: var(--color-text-secondary); } .pull-to-refresh-text { font-size: 0.75rem; - color: var(--text-secondary); + color: var(--color-text-secondary); text-align: center; white-space: nowrap; font-weight: 500; - background: var(--background-secondary); + background: var(--color-bg-elevated); padding: 0.25rem 0.75rem; border-radius: 1rem; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); From 65051c9c1fb5fcbeef0caff1458963cd3156a5df Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:25:13 +0200 Subject: [PATCH 05/23] fix(theme): apply theme colors to body element - Add background and text color to body - Ensures page background changes with theme --- src/styles/base/global.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/styles/base/global.css b/src/styles/base/global.css index f394f898..d80917ed 100644 --- a/src/styles/base/global.css +++ b/src/styles/base/global.css @@ -2,6 +2,8 @@ /* Body - keep only app-specific overrides */ body { + background: var(--color-bg); + color: var(--color-text); overscroll-behavior: none; -webkit-overflow-scrolling: touch; overflow-x: hidden; From 69febf435634e7d87182e5529e1d3242d626665a Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:34:54 +0200 Subject: [PATCH 06/23] refactor(theme): remove localStorage, use only Nostr for persistence - Remove localStorage.setItem/getItem from theme.ts - Simplify early boot script to just default to system theme - Theme now loads purely from NIP-78 settings - Prevents race conditions between localStorage and Nostr settings --- index.html | 16 ++-------------- src/utils/theme.ts | 22 +--------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/index.html b/index.html index c7ca0399..da4ed5f0 100644 --- a/index.html +++ b/index.html @@ -26,21 +26,9 @@ - + diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 44907f17..30c90d1d 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -32,33 +32,13 @@ export function applyTheme(theme: Theme): void { // 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') + // The CSS media query handles the color changes automatically } 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' -} - From 129aced1a2a06c02b6db073b3e7dcee2991ef72c Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:39:13 +0200 Subject: [PATCH 07/23] feat(theme): add color theme variants for light and dark modes - Add darkColorTheme: black, midnight (default), charcoal - Add lightColorTheme: paper-white (default), sepia, ivory - Extend UserSettings with color theme fields - Update ThemeSettings UI to show color options - Add CSS variables for all color theme variants - Sepia and Ivory have warm, reading-friendly palettes - Black offers true black for OLED screens - All color themes sync via Nostr (NIP-78) --- src/components/Settings/ThemeSettings.tsx | 65 +++++++++++- src/hooks/useSettings.ts | 8 +- src/services/settingsService.ts | 2 + src/styles/base/variables.css | 121 ++++++++++++++++++++++ src/utils/theme.ts | 25 ++++- 5 files changed, 215 insertions(+), 6 deletions(-) diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx index 10e7c990..22cbb662 100644 --- a/src/components/Settings/ThemeSettings.tsx +++ b/src/components/Settings/ThemeSettings.tsx @@ -10,6 +10,12 @@ interface ThemeSettingsProps { const ThemeSettings: React.FC = ({ settings, onUpdate }) => { const currentTheme = settings.theme ?? 'system' + const currentDarkColor = settings.darkColorTheme ?? 'midnight' + const currentLightColor = settings.lightColorTheme ?? 'paper-white' + + // Determine which color picker to show based on current theme + const showDarkColors = currentTheme === 'dark' || currentTheme === 'system' + const showLightColors = currentTheme === 'light' || currentTheme === 'system' return (
@@ -41,9 +47,66 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => />
+ + {showDarkColors && ( +
+ +
+ + + +
+
+ )} + + {showLightColors && ( +
+ +
+ + + +
+
+ )} ) } export default ThemeSettings - diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index 616f21f2..b2d1e36e 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -50,8 +50,12 @@ export function useSettings({ relayPool, eventStore, pubkey, accountManager }: U console.log('🎨 Applying settings styles:', { fontKey, fontSize: settings.fontSize, theme: settings.theme }) - // Apply theme (defaults to 'system' if not set) - applyTheme(settings.theme ?? 'system') + // Apply theme with color variants (defaults to 'system' if not set) + applyTheme( + settings.theme ?? 'system', + settings.darkColorTheme ?? 'midnight', + settings.lightColorTheme ?? 'paper-white' + ) // 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 f8f4be97..187e0e29 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -49,6 +49,8 @@ export interface UserSettings { autoCollapseSidebarOnMobile?: boolean // Auto-collapse sidebar on mobile (default: true) // Theme preference theme?: 'dark' | 'light' | 'system' // default: system + darkColorTheme?: 'black' | 'midnight' | 'charcoal' // default: midnight + lightColorTheme?: 'paper-white' | 'sepia' | 'ivory' // default: paper-white } export async function loadSettings( diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css index cea7cd74..5c6c8070 100644 --- a/src/styles/base/variables.css +++ b/src/styles/base/variables.css @@ -110,4 +110,125 @@ } } +/* Dark Color Theme Variants */ +/* Midnight (default) - current zinc palette */ +:root.dark-midnight { + --color-bg: #18181b; /* zinc-900 */ + --color-bg-elevated: #27272a; /* zinc-800 */ + --color-bg-subtle: #1e1e1e; + --color-border: #3f3f46; /* zinc-700 */ + --color-border-subtle: #52525b; /* zinc-600 */ +} + +/* Black - true black for OLED */ +:root.dark-black { + --color-bg: #000000; /* true black */ + --color-bg-elevated: #0a0a0a; /* very dark gray */ + --color-bg-subtle: #050505; + --color-border: #1a1a1a; + --color-border-subtle: #2a2a2a; +} + +/* Charcoal - warmer, softer dark */ +:root.dark-charcoal { + --color-bg: #1c1c1e; /* warmer dark */ + --color-bg-elevated: #2c2c2e; + --color-bg-subtle: #242426; + --color-border: #3a3a3c; + --color-border-subtle: #48484a; +} + +/* Light Color Theme Variants */ +/* Paper White (default) - pure white */ +:root.light-paper-white { + --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 */ +} + +/* Sepia - warm, reading-friendly */ +:root.light-sepia { + --color-bg: #f4f1ea; /* warm beige */ + --color-bg-elevated: #ebe6db; /* darker beige */ + --color-bg-subtle: #f9f6f0; /* lighter beige */ + --color-border: #d4cfc4; /* warm gray border */ + --color-border-subtle: #c4bfb4; + --color-text: #2d2a24; /* warm dark brown */ + --color-text-secondary: #5d5a54; + --color-text-muted: #8d8a84; +} + +/* Ivory - soft, creamy */ +:root.light-ivory { + --color-bg: #fffff0; /* ivory */ + --color-bg-elevated: #faf8f0; /* cream */ + --color-bg-subtle: #fefef8; + --color-border: #e8e6de; + --color-border-subtle: #d8d6ce; + --color-text: #1a1a18; /* near black with warm tint */ + --color-text-secondary: #4a4a48; + --color-text-muted: #7a7a78; +} + +/* System theme color variants */ +@media (prefers-color-scheme: dark) { + :root.theme-system.dark-midnight { + --color-bg: #18181b; + --color-bg-elevated: #27272a; + --color-bg-subtle: #1e1e1e; + --color-border: #3f3f46; + --color-border-subtle: #52525b; + } + + :root.theme-system.dark-black { + --color-bg: #000000; + --color-bg-elevated: #0a0a0a; + --color-bg-subtle: #050505; + --color-border: #1a1a1a; + --color-border-subtle: #2a2a2a; + } + + :root.theme-system.dark-charcoal { + --color-bg: #1c1c1e; + --color-bg-elevated: #2c2c2e; + --color-bg-subtle: #242426; + --color-border: #3a3a3c; + --color-border-subtle: #48484a; + } +} + +@media (prefers-color-scheme: light) { + :root.theme-system.light-paper-white { + --color-bg: #ffffff; + --color-bg-elevated: #f5f5f5; + --color-bg-subtle: #fafafa; + --color-border: #e5e7eb; + --color-border-subtle: #d1d5db; + } + + :root.theme-system.light-sepia { + --color-bg: #f4f1ea; + --color-bg-elevated: #ebe6db; + --color-bg-subtle: #f9f6f0; + --color-border: #d4cfc4; + --color-border-subtle: #c4bfb4; + --color-text: #2d2a24; + --color-text-secondary: #5d5a54; + --color-text-muted: #8d8a84; + } + + :root.theme-system.light-ivory { + --color-bg: #fffff0; + --color-bg-elevated: #faf8f0; + --color-bg-subtle: #fefef8; + --color-border: #e8e6de; + --color-border-subtle: #d8d6ce; + --color-text: #1a1a18; + --color-text-secondary: #4a4a48; + --color-text-muted: #7a7a78; + } +} + diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 30c90d1d..9bbc6796 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -1,4 +1,6 @@ export type Theme = 'dark' | 'light' | 'system' +export type DarkColorTheme = 'black' | 'midnight' | 'charcoal' +export type LightColorTheme = 'paper-white' | 'sepia' | 'ivory' let mediaQueryListener: ((e: MediaQueryListEvent) => void) | null = null @@ -11,14 +13,21 @@ export function getSystemTheme(): 'dark' | 'light' { } /** - * Apply theme to the document root element + * Apply theme and color variant to the document root element * Handles 'system' theme by listening to OS preference changes */ -export function applyTheme(theme: Theme): void { +export function applyTheme( + theme: Theme, + darkColorTheme: DarkColorTheme = 'midnight', + lightColorTheme: LightColorTheme = 'paper-white' +): void { const root = document.documentElement // Remove existing theme classes root.classList.remove('theme-dark', 'theme-light', 'theme-system') + // Remove existing color theme classes + root.classList.remove('dark-black', 'dark-midnight', 'dark-charcoal') + root.classList.remove('light-paper-white', 'light-sepia', 'light-ivory') // Clean up previous media query listener if exists if (mediaQueryListener) { @@ -29,6 +38,10 @@ export function applyTheme(theme: Theme): void { if (theme === 'system') { root.classList.add('theme-system') + // Apply color themes for system mode (CSS will handle media query) + root.classList.add(`dark-${darkColorTheme}`) + root.classList.add(`light-${lightColorTheme}`) + // Listen for system theme changes mediaQueryListener = (e: MediaQueryListEvent) => { console.log('🎨 System theme changed to:', e.matches ? 'dark' : 'light') @@ -38,7 +51,13 @@ export function applyTheme(theme: Theme): void { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', mediaQueryListener) } else { root.classList.add(`theme-${theme}`) + // Apply appropriate color theme based on light/dark + if (theme === 'dark') { + root.classList.add(`dark-${darkColorTheme}`) + } else { + root.classList.add(`light-${lightColorTheme}`) + } } - console.log('🎨 Applied theme:', theme) + console.log('🎨 Applied theme:', theme, 'with colors:', { dark: darkColorTheme, light: lightColorTheme }) } From 3f8869fd75d47e2c8575f57eb126d8f9d9d895f2 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 09:58:47 +0200 Subject: [PATCH 08/23] refactor(theme): show color swatches instead of text labels - Replace text buttons with color swatches for theme selection - Use actual background colors to preview each theme - Add border for white swatch to make it visible - Tooltips show theme names on hover --- src/components/Settings/ThemeSettings.tsx | 84 ++++++++++------------- 1 file changed, 38 insertions(+), 46 deletions(-) diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx index 22cbb662..a65c47d8 100644 --- a/src/components/Settings/ThemeSettings.tsx +++ b/src/components/Settings/ThemeSettings.tsx @@ -17,6 +17,19 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => const showDarkColors = currentTheme === 'dark' || currentTheme === 'system' const showLightColors = currentTheme === 'light' || currentTheme === 'system' + // Color definitions for swatches + const darkColors = { + black: '#000000', + midnight: '#18181b', + charcoal: '#1c1c1e' + } + + const lightColors = { + 'paper-white': '#ffffff', + sepia: '#f4f1ea', + ivory: '#fffff0' + } + return (

Theme

@@ -50,58 +63,37 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => {showDarkColors && (
- -
- - - + +
+ {Object.entries(darkColors).map(([key, color]) => ( +
onUpdate({ darkColorTheme: key as any })} + title={key.charAt(0).toUpperCase() + key.slice(1)} + /> + ))}
)} {showLightColors && (
- -
- - - + +
+ {Object.entries(lightColors).map(([key, color]) => ( +
onUpdate({ lightColorTheme: key as any })} + title={key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} + /> + ))}
)} From 689963c0415d76b6c534eb5b9af8db7adf14f2ea Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:00:21 +0200 Subject: [PATCH 09/23] refactor(theme): change default light theme to sepia - Update default from paper-white to sepia for warmer reading - Midnight remains default for dark mode - Sepia provides warm, eye-friendly tones for light mode --- src/components/Settings/ThemeSettings.tsx | 2 +- src/hooks/useSettings.ts | 2 +- src/services/settingsService.ts | 2 +- src/utils/theme.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx index a65c47d8..42755067 100644 --- a/src/components/Settings/ThemeSettings.tsx +++ b/src/components/Settings/ThemeSettings.tsx @@ -11,7 +11,7 @@ interface ThemeSettingsProps { const ThemeSettings: React.FC = ({ settings, onUpdate }) => { const currentTheme = settings.theme ?? 'system' const currentDarkColor = settings.darkColorTheme ?? 'midnight' - const currentLightColor = settings.lightColorTheme ?? 'paper-white' + const currentLightColor = settings.lightColorTheme ?? 'sepia' // Determine which color picker to show based on current theme const showDarkColors = currentTheme === 'dark' || currentTheme === 'system' diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts index b2d1e36e..a6b6ff6d 100644 --- a/src/hooks/useSettings.ts +++ b/src/hooks/useSettings.ts @@ -54,7 +54,7 @@ export function useSettings({ relayPool, eventStore, pubkey, accountManager }: U applyTheme( settings.theme ?? 'system', settings.darkColorTheme ?? 'midnight', - settings.lightColorTheme ?? 'paper-white' + settings.lightColorTheme ?? 'sepia' ) // Load font first and wait for it to be ready diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts index 187e0e29..021b6655 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -50,7 +50,7 @@ export interface UserSettings { // Theme preference theme?: 'dark' | 'light' | 'system' // default: system darkColorTheme?: 'black' | 'midnight' | 'charcoal' // default: midnight - lightColorTheme?: 'paper-white' | 'sepia' | 'ivory' // default: paper-white + lightColorTheme?: 'paper-white' | 'sepia' | 'ivory' // default: sepia } export async function loadSettings( diff --git a/src/utils/theme.ts b/src/utils/theme.ts index 9bbc6796..7c425875 100644 --- a/src/utils/theme.ts +++ b/src/utils/theme.ts @@ -19,7 +19,7 @@ export function getSystemTheme(): 'dark' | 'light' { export function applyTheme( theme: Theme, darkColorTheme: DarkColorTheme = 'midnight', - lightColorTheme: LightColorTheme = 'paper-white' + lightColorTheme: LightColorTheme = 'sepia' ): void { const root = document.documentElement From 18db9059745af88d7bd3847f02f1090f4ec08ea3 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:00:48 +0200 Subject: [PATCH 10/23] refactor(theme): rename labels from 'Colors' to 'Theme' - Change 'Dark Colors' to 'Dark Theme' - Change 'Light Colors' to 'Light Theme' - More consistent and clearer labeling --- src/components/Settings/ThemeSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx index 42755067..48b8aab3 100644 --- a/src/components/Settings/ThemeSettings.tsx +++ b/src/components/Settings/ThemeSettings.tsx @@ -63,7 +63,7 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => {showDarkColors && (
- +
{Object.entries(darkColors).map(([key, color]) => (
= ({ settings, onUpdate }) => {showLightColors && (
- +
{Object.entries(lightColors).map(([key, color]) => (
Date: Tue, 14 Oct 2025 10:02:43 +0200 Subject: [PATCH 11/23] fix(theme): update reading progress indicator to use theme colors - Replace hard-coded dark background with --color-bg-elevated - Use --color-border for progress track - Use --color-primary for progress bar - Use --color-text-muted for percentage text - Indicator now adapts to light/dark themes --- src/components/ReadingProgressIndicator.tsx | 27 +++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/ReadingProgressIndicator.tsx b/src/components/ReadingProgressIndicator.tsx index ac50e7e3..cd47932a 100644 --- a/src/components/ReadingProgressIndicator.tsx +++ b/src/components/ReadingProgressIndicator.tsx @@ -29,28 +29,39 @@ export const ReadingProgressIndicator: React.FC = return (
-
+
{showPercentage && ( -
+
{isComplete ? '✓' : `${clampedProgress}%`}
)} From 2a422fbeb9c2e8f1350af86893cc141eed34b3c4 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:05:10 +0200 Subject: [PATCH 12/23] fix(theme): use darker background for app body - Change body background from --color-bg to --color-bg-subtle - Creates visual depth and hierarchy between app bg and content - Content panels now stand out more against the background --- src/styles/base/global.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/base/global.css b/src/styles/base/global.css index d80917ed..359575d5 100644 --- a/src/styles/base/global.css +++ b/src/styles/base/global.css @@ -2,7 +2,7 @@ /* Body - keep only app-specific overrides */ body { - background: var(--color-bg); + background: var(--color-bg-subtle); color: var(--color-text); overscroll-behavior: none; -webkit-overflow-scrolling: touch; From 17480dddbfc9669bede0fe83aa1669ec6a1a2ed7 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:06:43 +0200 Subject: [PATCH 13/23] fix(theme): improve text contrast in dark color themes - Add text color definitions to all dark theme variants - Ensure bright text (zinc-200) for readability on dark backgrounds - Update --color-bg-subtle to be darker for better hierarchy - Fixes low contrast issue where text was barely readable --- src/styles/base/variables.css | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css index 5c6c8070..48bae9d5 100644 --- a/src/styles/base/variables.css +++ b/src/styles/base/variables.css @@ -115,27 +115,36 @@ :root.dark-midnight { --color-bg: #18181b; /* zinc-900 */ --color-bg-elevated: #27272a; /* zinc-800 */ - --color-bg-subtle: #1e1e1e; + --color-bg-subtle: #0f0f11; /* darker than 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 */ } /* Black - true black for OLED */ :root.dark-black { --color-bg: #000000; /* true black */ --color-bg-elevated: #0a0a0a; /* very dark gray */ - --color-bg-subtle: #050505; + --color-bg-subtle: #000000; /* true black for body */ --color-border: #1a1a1a; --color-border-subtle: #2a2a2a; + --color-text: #e4e4e7; /* zinc-200 */ + --color-text-secondary: #a1a1aa; /* zinc-400 */ + --color-text-muted: #71717a; /* zinc-500 */ } /* Charcoal - warmer, softer dark */ :root.dark-charcoal { --color-bg: #1c1c1e; /* warmer dark */ --color-bg-elevated: #2c2c2e; - --color-bg-subtle: #242426; + --color-bg-subtle: #16161a; /* darker charcoal */ --color-border: #3a3a3c; --color-border-subtle: #48484a; + --color-text: #e4e4e7; /* zinc-200 */ + --color-text-secondary: #a1a1aa; /* zinc-400 */ + --color-text-muted: #71717a; /* zinc-500 */ } /* Light Color Theme Variants */ @@ -177,25 +186,34 @@ :root.theme-system.dark-midnight { --color-bg: #18181b; --color-bg-elevated: #27272a; - --color-bg-subtle: #1e1e1e; + --color-bg-subtle: #0f0f11; --color-border: #3f3f46; --color-border-subtle: #52525b; + --color-text: #e4e4e7; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; } :root.theme-system.dark-black { --color-bg: #000000; --color-bg-elevated: #0a0a0a; - --color-bg-subtle: #050505; + --color-bg-subtle: #000000; --color-border: #1a1a1a; --color-border-subtle: #2a2a2a; + --color-text: #e4e4e7; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; } :root.theme-system.dark-charcoal { --color-bg: #1c1c1e; --color-bg-elevated: #2c2c2e; - --color-bg-subtle: #242426; + --color-bg-subtle: #16161a; --color-border: #3a3a3c; --color-border-subtle: #48484a; + --color-text: #e4e4e7; + --color-text-secondary: #a1a1aa; + --color-text-muted: #71717a; } } From ebdfa3b5a3264c99b7949e3edd79fac6d16640e9 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:10:57 +0200 Subject: [PATCH 14/23] fix(lint): replace 'any' types with proper type definitions - Add DarkColorTheme and LightColorTheme type definitions - Replace 'as any' with proper type assertions - All eslint and TypeScript checks now pass --- src/components/Settings/ThemeSettings.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/Settings/ThemeSettings.tsx b/src/components/Settings/ThemeSettings.tsx index 48b8aab3..36d764a9 100644 --- a/src/components/Settings/ThemeSettings.tsx +++ b/src/components/Settings/ThemeSettings.tsx @@ -3,6 +3,9 @@ import { faSun, faMoon, faDesktop } from '@fortawesome/free-solid-svg-icons' import { UserSettings } from '../../services/settingsService' import IconButton from '../IconButton' +type DarkColorTheme = 'black' | 'midnight' | 'charcoal' +type LightColorTheme = 'paper-white' | 'sepia' | 'ivory' + interface ThemeSettingsProps { settings: UserSettings onUpdate: (updates: Partial) => void @@ -70,7 +73,7 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => key={key} className={`color-swatch ${currentDarkColor === key ? 'active' : ''}`} style={{ backgroundColor: color }} - onClick={() => onUpdate({ darkColorTheme: key as any })} + onClick={() => onUpdate({ darkColorTheme: key as DarkColorTheme })} title={key.charAt(0).toUpperCase() + key.slice(1)} /> ))} @@ -90,7 +93,7 @@ const ThemeSettings: React.FC = ({ settings, onUpdate }) => backgroundColor: color, border: color === '#ffffff' ? '2px solid #e5e7eb' : '1px solid #e5e7eb' }} - onClick={() => onUpdate({ lightColorTheme: key as any })} + onClick={() => onUpdate({ lightColorTheme: key as LightColorTheme })} title={key.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} /> ))} From bca6458e447a44ecdbe3a41baace0808c00e33f5 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:12:22 +0200 Subject: [PATCH 15/23] fix(theme): improve highlight contrast in light themes - Use darker yellow (yellow-400 instead of yellow-300) for better visibility - Use darker orange (orange-600 instead of orange-500) - Sepia theme uses even darker highlights (yellow-500, red-600) - Ensures text and icons remain visible on highlighted text - Applies to all light theme variants and system mode --- src/styles/base/variables.css | 37 +++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css index 48bae9d5..64e18a4c 100644 --- a/src/styles/base/variables.css +++ b/src/styles/base/variables.css @@ -73,6 +73,11 @@ --color-text-muted: #6b7280; /* gray-500 */ --color-primary: #4f46e5; /* indigo-600 */ --color-primary-hover: #4338ca; /* indigo-700 */ + + /* Darker highlight colors for light theme - better contrast */ + --highlight-color-mine: #facc15; /* yellow-400 - darker than yellow-300 */ + --highlight-color-friends: #ea580c; /* orange-600 - darker than orange-500 */ + --highlight-color-nostrverse: #7c3aed; /* purple-600 - same as dark */ } /* System theme - follow OS preference */ @@ -107,6 +112,11 @@ --color-text-muted: #6b7280; --color-primary: #4f46e5; --color-primary-hover: #4338ca; + + /* Darker highlight colors for light theme */ + --highlight-color-mine: #facc15; + --highlight-color-friends: #ea580c; + --highlight-color-nostrverse: #7c3aed; } } @@ -155,6 +165,11 @@ --color-bg-subtle: #fafafa; /* gray-50 */ --color-border: #e5e7eb; /* gray-200 */ --color-border-subtle: #d1d5db; /* gray-300 */ + + /* Darker highlights for better contrast */ + --highlight-color-mine: #facc15; + --highlight-color-friends: #ea580c; + --highlight-color-nostrverse: #7c3aed; } /* Sepia - warm, reading-friendly */ @@ -167,6 +182,11 @@ --color-text: #2d2a24; /* warm dark brown */ --color-text-secondary: #5d5a54; --color-text-muted: #8d8a84; + + /* Darker highlights for better contrast on warm bg */ + --highlight-color-mine: #eab308; /* yellow-500 - even darker for sepia */ + --highlight-color-friends: #dc2626; /* red-600 - darker for warm bg */ + --highlight-color-nostrverse: #7c3aed; /* purple-600 */ } /* Ivory - soft, creamy */ @@ -179,6 +199,11 @@ --color-text: #1a1a18; /* near black with warm tint */ --color-text-secondary: #4a4a48; --color-text-muted: #7a7a78; + + /* Darker highlights for better contrast on ivory */ + --highlight-color-mine: #facc15; + --highlight-color-friends: #ea580c; + --highlight-color-nostrverse: #7c3aed; } /* System theme color variants */ @@ -224,6 +249,10 @@ --color-bg-subtle: #fafafa; --color-border: #e5e7eb; --color-border-subtle: #d1d5db; + + --highlight-color-mine: #facc15; + --highlight-color-friends: #ea580c; + --highlight-color-nostrverse: #7c3aed; } :root.theme-system.light-sepia { @@ -235,6 +264,10 @@ --color-text: #2d2a24; --color-text-secondary: #5d5a54; --color-text-muted: #8d8a84; + + --highlight-color-mine: #eab308; + --highlight-color-friends: #dc2626; + --highlight-color-nostrverse: #7c3aed; } :root.theme-system.light-ivory { @@ -246,6 +279,10 @@ --color-text: #1a1a18; --color-text-secondary: #4a4a48; --color-text-muted: #7a7a78; + + --highlight-color-mine: #facc15; + --highlight-color-friends: #ea580c; + --highlight-color-nostrverse: #7c3aed; } } From 5c7b413a8d332229e9eca07f59c9ffc1fa90f9af Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:23:55 +0200 Subject: [PATCH 16/23] fix(theme): use consistent yellow-300 highlight color across all themes - Revert to yellow-300 (#fde047) for all light and dark themes - Use consistent Tailwind palette: yellow-300, orange-500, purple-600 - Previous darker colors were causing inconsistency with design system - Ensures highlights use the same color values across all theme variants --- src/styles/base/variables.css | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/styles/base/variables.css b/src/styles/base/variables.css index 64e18a4c..d70415f1 100644 --- a/src/styles/base/variables.css +++ b/src/styles/base/variables.css @@ -74,10 +74,10 @@ --color-primary: #4f46e5; /* indigo-600 */ --color-primary-hover: #4338ca; /* indigo-700 */ - /* Darker highlight colors for light theme - better contrast */ - --highlight-color-mine: #facc15; /* yellow-400 - darker than yellow-300 */ - --highlight-color-friends: #ea580c; /* orange-600 - darker than orange-500 */ - --highlight-color-nostrverse: #7c3aed; /* purple-600 - same as dark */ + /* Highlight colors for light theme - use same Tailwind colors */ + --highlight-color-mine: #fde047; /* yellow-300 */ + --highlight-color-friends: #f97316; /* orange-500 */ + --highlight-color-nostrverse: #9333ea; /* purple-600 */ } /* System theme - follow OS preference */ @@ -113,10 +113,10 @@ --color-primary: #4f46e5; --color-primary-hover: #4338ca; - /* Darker highlight colors for light theme */ - --highlight-color-mine: #facc15; - --highlight-color-friends: #ea580c; - --highlight-color-nostrverse: #7c3aed; + /* Standard highlight colors */ + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } } @@ -166,10 +166,10 @@ --color-border: #e5e7eb; /* gray-200 */ --color-border-subtle: #d1d5db; /* gray-300 */ - /* Darker highlights for better contrast */ - --highlight-color-mine: #facc15; - --highlight-color-friends: #ea580c; - --highlight-color-nostrverse: #7c3aed; + /* Standard highlight colors */ + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } /* Sepia - warm, reading-friendly */ @@ -183,10 +183,10 @@ --color-text-secondary: #5d5a54; --color-text-muted: #8d8a84; - /* Darker highlights for better contrast on warm bg */ - --highlight-color-mine: #eab308; /* yellow-500 - even darker for sepia */ - --highlight-color-friends: #dc2626; /* red-600 - darker for warm bg */ - --highlight-color-nostrverse: #7c3aed; /* purple-600 */ + /* Standard highlight colors */ + --highlight-color-mine: #fde047; /* yellow-300 */ + --highlight-color-friends: #f97316; /* orange-500 */ + --highlight-color-nostrverse: #9333ea; /* purple-600 */ } /* Ivory - soft, creamy */ @@ -200,10 +200,10 @@ --color-text-secondary: #4a4a48; --color-text-muted: #7a7a78; - /* Darker highlights for better contrast on ivory */ - --highlight-color-mine: #facc15; - --highlight-color-friends: #ea580c; - --highlight-color-nostrverse: #7c3aed; + /* Standard highlight colors */ + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } /* System theme color variants */ @@ -250,9 +250,9 @@ --color-border: #e5e7eb; --color-border-subtle: #d1d5db; - --highlight-color-mine: #facc15; - --highlight-color-friends: #ea580c; - --highlight-color-nostrverse: #7c3aed; + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } :root.theme-system.light-sepia { @@ -265,9 +265,9 @@ --color-text-secondary: #5d5a54; --color-text-muted: #8d8a84; - --highlight-color-mine: #eab308; - --highlight-color-friends: #dc2626; - --highlight-color-nostrverse: #7c3aed; + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } :root.theme-system.light-ivory { @@ -280,9 +280,9 @@ --color-text-secondary: #4a4a48; --color-text-muted: #7a7a78; - --highlight-color-mine: #facc15; - --highlight-color-friends: #ea580c; - --highlight-color-nostrverse: #7c3aed; + --highlight-color-mine: #fde047; + --highlight-color-friends: #f97316; + --highlight-color-nostrverse: #9333ea; } } From 168095e133665ed70fb1fb6ace5327f517ec69ce Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:29:11 +0200 Subject: [PATCH 17/23] fix(ui): improve Highlights tab readability in light mode - Use semantic text color (--color-text) for tab label in active state - Keep highlight color for icon and bottom border as visual accent - Ensures text is always readable regardless of theme - Fixes contrast issues on /me page Highlights tab --- src/styles/components/me.css | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/styles/components/me.css b/src/styles/components/me.css index a235be33..1a602a8d 100644 --- a/src/styles/components/me.css +++ b/src/styles/components/me.css @@ -37,12 +37,18 @@ border-bottom-color: var(--color-primary); } -/* Highlights tab uses the user's custom "my highlights" color */ +/* Highlights tab uses the user's custom "my highlights" color for the border */ +/* But uses semantic text color for better readability */ .me-tab[data-tab="highlights"].active { - color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ + color: var(--color-text); border-bottom-color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ } +/* Icon gets the highlight color for visual identity */ +.me-tab[data-tab="highlights"].active svg { + color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ +} + /* Reading List tab uses blue color to match bookmarks icon */ .me-tab[data-tab="reading-list"].active { color: var(--color-primary); From 47d1335842c288a218248663926a5dbb8e364352 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:30:27 +0200 Subject: [PATCH 18/23] fix(ui): add background to Highlights tab for better contrast - Add --color-bg-elevated background to active Highlights tab - Improves contrast of yellow highlight color in light mode - Creates visual separation while maintaining highlight color identity - Keeps yellow text and border for consistent highlight theming --- src/styles/components/me.css | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/styles/components/me.css b/src/styles/components/me.css index 1a602a8d..c5b12175 100644 --- a/src/styles/components/me.css +++ b/src/styles/components/me.css @@ -37,16 +37,12 @@ border-bottom-color: var(--color-primary); } -/* Highlights tab uses the user's custom "my highlights" color for the border */ -/* But uses semantic text color for better readability */ +/* Highlights tab uses the user's custom "my highlights" color */ +/* With a darker background for better contrast in light mode */ .me-tab[data-tab="highlights"].active { - color: var(--color-text); - border-bottom-color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ -} - -/* Icon gets the highlight color for visual identity */ -.me-tab[data-tab="highlights"].active svg { color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ + border-bottom-color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ + background: var(--color-bg-elevated); } /* Reading List tab uses blue color to match bookmarks icon */ From a95f9b522bbdd86152ac44af7284f75ddbd4b12f Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:31:35 +0200 Subject: [PATCH 19/23] refactor(ui): simplify Me page tab labels - Remove count numbers from all tabs (cleaner UI) - Rename "Reading List" to "Bookmarks" (clearer naming) - Keep tab names: Highlights, Bookmarks, Archive, Writings - Reduces visual clutter and improves readability --- src/components/Me.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Me.tsx b/src/components/Me.tsx index d0683d88..2e4aa80a 100644 --- a/src/components/Me.tsx +++ b/src/components/Me.tsx @@ -376,7 +376,6 @@ const Me: React.FC = ({ relayPool, activeTab: propActiveTab, pubkey: pr > Highlights - ({highlights.length}) {isOwnProfile && ( <> @@ -386,8 +385,7 @@ const Me: React.FC = ({ relayPool, activeTab: propActiveTab, pubkey: pr onClick={() => navigate('/me/reading-list')} > - Reading List - ({allIndividualBookmarks.length}) + Bookmarks )} @@ -407,7 +404,6 @@ const Me: React.FC = ({ relayPool, activeTab: propActiveTab, pubkey: pr > Writings - ({writings.length})
From 6537650757b9e8027373a3ea9ff79335cf12e1a7 Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:32:31 +0200 Subject: [PATCH 20/23] feat(ui): apply highlight marker style to active Highlights tab - Use actual highlight visual treatment (marker style) on tab - Text remains in semantic color (--color-text) for readability - Background uses 35% highlight color blend with glow effect - Hover state intensifies to 50% for better interaction feedback - Creates consistent visual language between tabs and content highlights --- src/styles/components/me.css | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/styles/components/me.css b/src/styles/components/me.css index c5b12175..47c67378 100644 --- a/src/styles/components/me.css +++ b/src/styles/components/me.css @@ -37,12 +37,17 @@ border-bottom-color: var(--color-primary); } -/* Highlights tab uses the user's custom "my highlights" color */ -/* With a darker background for better contrast in light mode */ +/* Highlights tab uses the actual highlight style when active */ .me-tab[data-tab="highlights"].active { - color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ + color: var(--color-text); border-bottom-color: var(--highlight-color-mine, rgb(253 224 71)); /* yellow-300 */ - background: var(--color-bg-elevated); + background: color-mix(in srgb, var(--highlight-color-mine, rgb(253 224 71)) 35%, transparent); + box-shadow: 0 0 8px color-mix(in srgb, var(--highlight-color-mine, rgb(253 224 71)) 20%, transparent); +} + +.me-tab[data-tab="highlights"].active:hover { + background: color-mix(in srgb, var(--highlight-color-mine, rgb(253 224 71)) 50%, transparent); + box-shadow: 0 0 12px color-mix(in srgb, var(--highlight-color-mine, rgb(253 224 71)) 30%, transparent); } /* Reading List tab uses blue color to match bookmarks icon */ From 93d0c1052b05dd30e8e9c39030039f1f4f60fdde Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:34:07 +0200 Subject: [PATCH 21/23] feat(ui): align highlight text with footer icons - Add 1.25rem left padding to highlight text content - Add 1.25rem left margin to highlight comments - Text now starts roughly where the fa-server icon ends - Improves visual alignment and readability of highlight cards --- src/styles/layout/highlights.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/layout/highlights.css b/src/styles/layout/highlights.css index 5fb2b3e4..397cd9d7 100644 --- a/src/styles/layout/highlights.css +++ b/src/styles/layout/highlights.css @@ -127,8 +127,8 @@ .highlight-item.level-nostrverse .highlight-quote-icon { color: var(--highlight-color-nostrverse, #9333ea); } .highlight-content { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 2.25rem 0.75rem; } -.highlight-text { margin: 0; padding: 0; font-style: italic; color: var(--color-text); line-height: 1.6; border-left: none; font-size: 0.95rem; } -.highlight-comment { margin-top: 0.5rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: var(--color-text); line-height: 1.5; } +.highlight-text { margin: 0; padding: 0 0 0 1.25rem; font-style: italic; color: var(--color-text); line-height: 1.6; border-left: none; font-size: 0.95rem; } +.highlight-comment { margin-top: 0.5rem; margin-left: 1.25rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: var(--color-text); line-height: 1.5; } /* Level-colored comments */ .highlight-item.level-mine .highlight-comment { background: color-mix(in srgb, var(--highlight-color-mine, #ffff00) 10%, transparent); border-left-color: var(--highlight-color-mine, #ffff00); } From 7426c9d1fccf8162a0e61e72955b1b437691557d Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:34:48 +0200 Subject: [PATCH 22/23] feat(ui): increase spacing between highlight cards - Increase gap from 0.75rem to 1rem in highlights list - Provides better visual breathing room between cards - Improves overall readability and card separation --- src/styles/layout/highlights.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/layout/highlights.css b/src/styles/layout/highlights.css index 397cd9d7..7997c5f4 100644 --- a/src/styles/layout/highlights.css +++ b/src/styles/layout/highlights.css @@ -80,7 +80,7 @@ .highlights-empty svg { color: var(--color-text-muted); margin-bottom: 0.5rem; } .empty-hint { font-size: 0.875rem; color: var(--color-text-muted); margin-top: 0.5rem; } -.highlights-list { overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; } +.highlights-list { overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 1rem; } .highlight-item { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 0; display: flex; transition: border-color 0.2s ease; position: relative; } .highlight-item:hover { border-color: var(--color-primary); } .highlight-item.selected { border-color: var(--color-primary); background: var(--color-bg-elevated); box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); } From f267df8f6026c37a47e6b78f679dd40a71bfa94a Mon Sep 17 00:00:00 2001 From: Gigi Date: Tue, 14 Oct 2025 10:35:34 +0200 Subject: [PATCH 23/23] feat(ui): increase bottom padding in highlight cards - Increase bottom padding from 0.75rem to 2.5rem - Reduces gap between cards from 1rem to 0.75rem (user edit) - Provides more breathing room between text and footer - Improves readability and visual balance --- src/styles/layout/highlights.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/layout/highlights.css b/src/styles/layout/highlights.css index 7997c5f4..4a2f72b3 100644 --- a/src/styles/layout/highlights.css +++ b/src/styles/layout/highlights.css @@ -80,7 +80,7 @@ .highlights-empty svg { color: var(--color-text-muted); margin-bottom: 0.5rem; } .empty-hint { font-size: 0.875rem; color: var(--color-text-muted); margin-top: 0.5rem; } -.highlights-list { overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 1rem; } +.highlights-list { overflow-y: auto; padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; } .highlight-item { background: var(--color-bg-subtle); border: 1px solid var(--color-border); border-radius: 8px; padding: 0; display: flex; transition: border-color 0.2s ease; position: relative; } .highlight-item:hover { border-color: var(--color-primary); } .highlight-item.selected { border-color: var(--color-primary); background: var(--color-bg-elevated); box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3); } @@ -126,7 +126,7 @@ .highlight-item.level-friends .highlight-quote-icon { color: var(--highlight-color-friends, #f97316); } .highlight-item.level-nostrverse .highlight-quote-icon { color: var(--highlight-color-nostrverse, #9333ea); } -.highlight-content { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 2.25rem 0.75rem; } +.highlight-content { flex: 1; display: flex; flex-direction: column; gap: 0.5rem; padding: 2.25rem 0.75rem 2.5rem; } .highlight-text { margin: 0; padding: 0 0 0 1.25rem; font-style: italic; color: var(--color-text); line-height: 1.6; border-left: none; font-size: 0.95rem; } .highlight-comment { margin-top: 0.5rem; margin-left: 1.25rem; padding: 0.75rem; border-left: 3px solid; border-radius: 4px; font-size: 0.875rem; color: var(--color-text); line-height: 1.5; }