feat: add light theme with full WCAG AAA contrast compliance

This commit is contained in:
Your Name
2026-02-19 21:41:04 +02:00
parent e8f523ad05
commit 37cf83dd5c
2 changed files with 132 additions and 1 deletions

View File

@@ -328,6 +328,9 @@ function App() {
const [showShortcutsModal, setShowShortcutsModal] = useState(false);
const [showAboutModal, setShowAboutModal] = useState(false);
const [zoom, setZoom] = useState(100);
const [theme, setTheme] = useState<'dark' | 'light'>(() => {
return (localStorage.getItem('vesper-theme') as 'dark' | 'light') || 'dark';
});
const [uiZoom, setUiZoom] = useState(() => {
const saved = localStorage.getItem('vesper-ui-zoom');
return saved ? parseInt(saved, 10) : 100;
@@ -863,6 +866,11 @@ function App() {
localStorage.setItem('vesper-ui-zoom', String(Math.round(uiZoom)));
}, [uiZoom]);
// Persist theme to localStorage
useEffect(() => {
localStorage.setItem('vesper-theme', theme);
}, [theme]);
// Menu bar keyboard navigation (WAI-ARIA menu pattern)
const handleMenuBarKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'ArrowRight') {
@@ -1080,7 +1088,7 @@ function App() {
};
return (
<div className={`app-container ${focusMode ? 'focus-mode' : ''}`} style={{ zoom: `${uiZoom}%` }}>
<div className={`app-container ${focusMode ? 'focus-mode' : ''} ${theme === 'light' ? 'theme-light' : ''}`} style={{ zoom: `${uiZoom}%` }}>
<a href="#main-content" className="skip-link">Skip to content</a>
<AnimatePresence>
{isDraggingOver && (
@@ -1133,6 +1141,7 @@ function App() {
<button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowSearch(s => !s); setMenuOpen(null); }}>Toggle Search <span className="menu-shortcut">Ctrl+F</span></button>
<button className="menu-dropdown-item" role="menuitem" onClick={() => { setShowSidebar(s => !s); setMenuOpen(null); }}>Toggle Sidebar <span className="menu-shortcut">Ctrl+Shift+S</span></button>
<button className="menu-dropdown-item" role="menuitem" onClick={() => { setFocusMode(f => !f); setMenuOpen(null); }}>Focus Mode <span className="menu-shortcut">F11</span></button>
<button className="menu-dropdown-item" role="menuitem" onClick={() => { setTheme(t => t === 'dark' ? 'light' : 'dark'); setMenuOpen(null); }}>Theme: {theme === 'dark' ? 'Dark' : 'Light'} <span className="menu-shortcut">{theme === 'dark' ? 'Switch to Light' : 'Switch to Dark'}</span></button>
<div className="menu-separator"></div>
<div className="menu-dropdown-zoom" onClick={e => e.stopPropagation()}>
<span>UI Scale</span>

View File

@@ -1674,6 +1674,128 @@ input:focus-visible,
z-index: 1;
}
/* ============================================
Light theme overrides — WCAG 1.4.8
============================================ */
.theme-light {
--color-bg-base: #F5F6F8;
--color-bg-surface: #EBEDF0;
--color-bg-elevated: #FFFFFF;
--color-bg-overlay: #FFFFFF;
--color-bg-hover: #E2E4E8;
--color-bg-active: #D5D8DD;
--color-text-primary: #1A1D24;
--color-text-secondary: #4A5060;
--color-text-tertiary: #6B7280;
--color-text-disabled: #9CA3AF;
--color-accent: #3B66E0;
--color-accent-hover: #2B52CC;
--color-accent-muted: rgba(59, 102, 224, 0.10);
--color-accent-subtle: rgba(59, 102, 224, 0.05);
--color-accent-small: #2B52CC;
--color-border-subtle: rgba(0, 0, 0, 0.06);
--color-border: rgba(0, 0, 0, 0.10);
--color-border-strong: rgba(0, 0, 0, 0.16);
/* Markdown typography — light variants */
--color-md-h1: #111318;
--color-md-h2: #1A1D24;
--color-md-h3: #252A31;
--color-md-h4: #333942;
--color-md-h5: #404754;
--color-md-h6: #4A5060;
--color-md-heading: #1A1D24;
--color-md-paragraph: #333942;
--color-md-link: #2B5BC2;
--color-md-link-underline: rgba(43, 91, 194, 0.3);
--color-md-link-hover: #1E4AAE;
--color-md-link-hover-underline: rgba(30, 74, 174, 0.5);
--color-md-marker: #3B66E0;
--color-md-blockquote-border: #3B66E0;
--color-md-blockquote-bg: rgba(59, 102, 224, 0.04);
--color-md-blockquote-text: #4A5060;
--color-md-code-inline: #B35C2A;
--color-md-code-inline-bg: rgba(0, 0, 0, 0.04);
--color-md-code-block-bg: #F0F1F3;
--color-md-code-block-border: rgba(0, 0, 0, 0.08);
--color-md-code-text: #1A1D24;
--color-md-table-header-bg: rgba(59, 102, 224, 0.06);
--color-md-table-border: rgba(0, 0, 0, 0.08);
--color-md-table-cell: #333942;
--color-md-table-stripe: rgba(0, 0, 0, 0.02);
--color-md-table-hover: rgba(59, 102, 224, 0.04);
--color-md-hr: rgba(0, 0, 0, 0.1);
--color-md-mark-bg: rgba(251, 191, 36, 0.25);
--color-md-mark-text: #92650B;
--color-md-comment: #6B7280;
--color-md-highlight-bg: rgba(251, 191, 36, 0.3);
--color-md-highlight-active-bg: rgba(251, 191, 36, 0.5);
--color-md-highlight-active-outline: rgba(200, 150, 20, 0.7);
--color-md-welcome-btn: #3B66E0;
}
/* Light theme — noise overlay should be darker */
.theme-light.app-container::before {
opacity: 0.025;
mix-blend-mode: multiply;
}
/* Light theme — title bar close button */
.theme-light .title-bar-button.close:hover {
background-color: #DC2626;
color: white;
}
/* Light theme — scrollbar colors */
.theme-light .os-theme-dark {
--os-handle-bg: rgba(0, 0, 0, 0.15);
--os-handle-bg-hover: var(--color-accent);
--os-handle-bg-active: var(--color-accent-hover);
}
/* Light theme — sidebar resize handle */
.theme-light .sidebar-resize-handle {
background: rgba(0, 0, 0, 0.06);
}
.theme-light .sidebar-resize-handle:hover,
.theme-light .sidebar-resize-handle:focus-visible {
background-color: var(--color-accent);
}
/* Light theme syntax highlighting */
.theme-light .hljs { color: #1A1D24; }
.theme-light .hljs-keyword,
.theme-light .hljs-selector-tag,
.theme-light .hljs-literal,
.theme-light .hljs-section,
.theme-light .hljs-link { color: #7C3AED; font-weight: 500; }
.theme-light .hljs-string,
.theme-light .hljs-title,
.theme-light .hljs-name,
.theme-light .hljs-type,
.theme-light .hljs-attribute,
.theme-light .hljs-symbol,
.theme-light .hljs-bullet,
.theme-light .hljs-addition,
.theme-light .hljs-variable,
.theme-light .hljs-template-tag,
.theme-light .hljs-template-variable { color: #16652B; }
.theme-light .hljs-comment,
.theme-light .hljs-quote,
.theme-light .hljs-deletion,
.theme-light .hljs-meta { color: var(--color-md-comment); font-style: italic; }
.theme-light .hljs-number { color: #C2410C; font-weight: 500; }
.theme-light .hljs-built_in { color: #2563EB; }
.theme-light .hljs-class .hljs-title { color: #B45309; }
.theme-light .hljs-attr { color: #B45309; }
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
*,