From 80ec9085ddc93c9612c828e699edb57416fdc226 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Thu, 18 Jun 2026 18:57:53 +0800 Subject: [PATCH] fix(theme): replace Gruvbox with Midnight, fix accent/info hue collisions and hardcoded colors - Replace Gruvbox preset with Midnight (deep blue-purple, violet accent) - Fix accent/info hue collisions in Nord, Monokai, Dracula, Solarized - Fix Solarized error/warning collision (error-h 25->5) and WCAG contrast - Make --color-skip-refresh-* follow --color-warning-h dynamically - Replace hardcoded rgba(24,144,255) in onboarding.css with --color-accent - Replace hardcoded #00B87A in import modals with --color-success --- locales/de.json | 2 +- locales/en.json | 2 +- locales/es.json | 2 +- locales/fr.json | 2 +- locales/he.json | 2 +- locales/ja.json | 2 +- locales/ko.json | 2 +- locales/ru.json | 2 +- locales/zh-CN.json | 2 +- locales/zh-TW.json | 2 +- static/css/components/batch-import-modal.css | 14 +- static/css/components/header.css | 8 +- static/css/components/import-modal.css | 8 +- static/css/onboarding.css | 12 +- static/css/tokens/colors.css | 152 ++++++++++++------- static/js/utils/uiHelpers.js | 11 +- templates/components/header.html | 6 +- 17 files changed, 137 insertions(+), 94 deletions(-) diff --git a/locales/de.json b/locales/de.json index 783ef8d3..daad230e 100644 --- a/locales/de.json +++ b/locales/de.json @@ -255,7 +255,7 @@ "presets": "Theme-Voreinstellungen", "default": "Standard", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/en.json b/locales/en.json index 12543251..b4c52e2f 100644 --- a/locales/en.json +++ b/locales/en.json @@ -255,7 +255,7 @@ "presets": "Theme Presets", "default": "Default", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/es.json b/locales/es.json index 4b00eb3e..11de9284 100644 --- a/locales/es.json +++ b/locales/es.json @@ -255,7 +255,7 @@ "presets": "Preajustes de tema", "default": "Predeterminado", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/fr.json b/locales/fr.json index 673244ff..8bd3035a 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -255,7 +255,7 @@ "presets": "Préréglages de thème", "default": "Par défaut", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/he.json b/locales/he.json index b6b3f6ef..e6da1428 100644 --- a/locales/he.json +++ b/locales/he.json @@ -255,7 +255,7 @@ "presets": "ערכות נושא מוגדרות", "default": "ברירת מחדל", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/ja.json b/locales/ja.json index cda982c4..9012ac58 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -255,7 +255,7 @@ "presets": "テーマプリセット", "default": "デフォルト", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/ko.json b/locales/ko.json index bccace89..d5fdbe33 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -255,7 +255,7 @@ "presets": "테마 프리셋", "default": "기본", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/ru.json b/locales/ru.json index 24dd8008..7729b395 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -255,7 +255,7 @@ "presets": "Предустановки тем", "default": "По умолчанию", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 0aabac47..a08697c2 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -255,7 +255,7 @@ "presets": "主题预设", "default": "默认", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 5930aab8..d1550428 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -255,7 +255,7 @@ "presets": "主題預設", "default": "預設", "nord": "Nord", - "gruvbox": "Gruvbox", + "midnight": "Midnight", "monokai": "Monokai", "dracula": "Dracula", "solarized": "Solarized", diff --git a/static/css/components/batch-import-modal.css b/static/css/components/batch-import-modal.css index 95cfa9db..f99b5112 100644 --- a/static/css/components/batch-import-modal.css +++ b/static/css/components/batch-import-modal.css @@ -389,7 +389,7 @@ } .stat-item.success { - border-left: 3px solid #00B87A; + border-left: 3px solid var(--color-success); } .stat-item.failed { @@ -455,7 +455,7 @@ .results-icon { font-size: 3em; - color: #00B87A; + color: var(--color-success); margin-bottom: var(--space-1); } @@ -493,7 +493,7 @@ } .result-card.success { - border-left: 3px solid #00B87A; + border-left: 3px solid var(--color-success); } .result-card.failed { @@ -582,8 +582,8 @@ } .result-item-status.success { - background: oklch(from #00B87A l c h / 0.2); - color: #00B87A; + background: color-mix(in oklch, var(--color-success) 20%, transparent); + color: var(--color-success); } .result-item-status.failed { @@ -661,11 +661,11 @@ /* Completed State */ .batch-progress-container.completed .progress-bar { - background: #00B87A; + background: var(--color-success); } .batch-progress-container.completed .status-icon { - color: #00B87A; + color: var(--color-success); } .batch-progress-container.completed .status-icon i { diff --git a/static/css/components/header.css b/static/css/components/header.css index 8cd54440..7265974d 100644 --- a/static/css/components/header.css +++ b/static/css/components/header.css @@ -472,8 +472,8 @@ background: oklch(62% 0.18 213); } -.preset-swatch-gruvbox { - background: oklch(58% 0.22 25); +.preset-swatch-midnight { + background: oklch(52% 0.15 300); } .preset-swatch-monokai { @@ -508,8 +508,8 @@ background: oklch(68% 0.18 213); } -[data-theme="dark"] .preset-swatch-gruvbox { - background: oklch(62% 0.22 25); +[data-theme="dark"] .preset-swatch-midnight { + background: oklch(68% 0.14 300); } [data-theme="dark"] .preset-swatch-monokai { diff --git a/static/css/components/import-modal.css b/static/css/components/import-modal.css index f56661cb..6cf38643 100644 --- a/static/css/components/import-modal.css +++ b/static/css/components/import-modal.css @@ -211,7 +211,7 @@ .lora-item.is-early-access { background: rgba(0, 184, 122, 0.05); - border-left: 4px solid #00B87A; + border-left: 4px solid var(--color-success); } .lora-item.missing-locally { @@ -310,7 +310,7 @@ .missing-lora-item.is-early-access { background: rgba(0, 184, 122, 0.05); - border-left: 3px solid #00B87A; + border-left: 3px solid var(--color-success); padding-left: 10px; } @@ -630,7 +630,7 @@ gap: 12px; padding: 12px 16px; background: rgba(0, 184, 122, 0.1); - border: 1px solid #00B87A; + border: 1px solid var(--color-success); border-radius: var(--border-radius-sm); color: var(--text-color); margin-bottom: var(--space-2); @@ -646,7 +646,7 @@ /* Specific styling for the early access warning container in import modal */ .early-access-warning .warning-icon { - color: #00B87A; + color: var(--color-success); font-size: 1.2em; } diff --git a/static/css/onboarding.css b/static/css/onboarding.css index 6de2a77c..592200aa 100644 --- a/static/css/onboarding.css +++ b/static/css/onboarding.css @@ -27,8 +27,8 @@ transition: var(--transition-slow); /* Add glow effect */ box-shadow: - 0 0 0 2px rgba(24, 144, 255, 0.3), - 0 0 20px rgba(24, 144, 255, 0.2), + 0 0 0 2px color-mix(in oklch, var(--color-accent) 30%, transparent), + 0 0 20px color-mix(in oklch, var(--color-accent) 20%, transparent), inset 0 0 0 1px rgba(255, 255, 255, 0.1); } @@ -221,14 +221,14 @@ @keyframes onboarding-pulse { 0%, 100% { box-shadow: - 0 0 0 2px rgba(24, 144, 255, 0.4), - 0 0 20px rgba(24, 144, 255, 0.3), + 0 0 0 2px color-mix(in oklch, var(--color-accent) 40%, transparent), + 0 0 20px color-mix(in oklch, var(--color-accent) 30%, transparent), inset 0 0 0 1px rgba(255, 255, 255, 0.1); } 50% { box-shadow: - 0 0 0 4px rgba(24, 144, 255, 0.6), - 0 0 30px rgba(24, 144, 255, 0.4), + 0 0 0 4px color-mix(in oklch, var(--color-accent) 60%, transparent), + 0 0 30px color-mix(in oklch, var(--color-accent) 40%, transparent), inset 0 0 0 1px rgba(255, 255, 255, 0.2); } } diff --git a/static/css/tokens/colors.css b/static/css/tokens/colors.css index e021394f..58683452 100644 --- a/static/css/tokens/colors.css +++ b/static/css/tokens/colors.css @@ -37,13 +37,13 @@ --color-error-border: color-mix(in oklch, var(--color-error) 50%, transparent); --color-info: oklch(var(--color-info-l) var(--color-info-c) var(--color-info-h)); - --color-info-bg: oklch(72% 0.2 220); - --color-info-text: oklch(28% 0.03 220); - --color-info-glow: oklch(72% 0.2 220 / 0.28); + --color-info-bg: oklch(var(--color-info-l) var(--color-info-c) var(--color-info-h)); + --color-info-text: oklch(28% 0.03 var(--color-info-h)); + --color-info-glow: oklch(var(--color-info-l) var(--color-info-c) var(--color-info-h) / 0.28); - --color-skip-refresh-bg: oklch(82% 0.12 45); - --color-skip-refresh-text: oklch(35% 0.02 45); - --color-skip-refresh-glow: oklch(82% 0.12 45 / 0.15); + --color-skip-refresh-bg: oklch(82% 0.12 var(--color-warning-h)); + --color-skip-refresh-text: oklch(35% 0.02 var(--color-warning-h)); + --color-skip-refresh-glow: oklch(82% 0.12 var(--color-warning-h) / 0.15); } :root { @@ -106,9 +106,9 @@ --status-info-bg: oklch(50% 0.10 190 / 0.25); --status-info-border: oklch(55% 0.12 195 / 0.3); - --color-info-bg: oklch(62% 0.18 220); - --color-info-text: oklch(98% 0.02 240); - --color-info-glow: oklch(62% 0.18 220 / 0.4); + --color-info-bg: oklch(62% 0.18 var(--color-info-h)); + --color-info-text: oklch(98% 0.02 var(--color-info-h)); + --color-info-glow: oklch(62% 0.18 var(--color-info-h) / 0.4); --color-error-bg: color-mix(in oklch, var(--color-error) 15%, transparent); --color-error-border: color-mix(in oklch, var(--color-error) 40%, transparent); @@ -125,7 +125,11 @@ --color-warning-h: 35; --color-warning-c: 0.18; --color-success-h: 130; + --color-error-l: 62%; + --color-error-c: 0.22; --color-error-h: 5; + --color-info-h: 195; + --color-info-c: 0.18; --bg-base: oklch(96% 0.01 240); --bg-elevated: oklch(98% 0.008 240 / 0.95); @@ -155,7 +159,11 @@ --color-warning-h: 35; --color-warning-c: 0.18; --color-success-h: 130; + --color-error-l: 65%; + --color-error-c: 0.22; --color-error-h: 5; + --color-info-h: 195; + --color-info-c: 0.18; --bg-base: oklch(20% 0.03 260); --bg-elevated: oklch(24% 0.03 260 / 0.98); @@ -178,66 +186,74 @@ --favorite-glow: oklch(78% 0.15 85 / 0.5); } -/* ── Preset: Gruvbox ───────────────────────────────────────── */ +/* ── Preset: Midnight ───────────────────────────────────────── */ -[data-theme-preset="gruvbox"] { - --color-accent-h: 25; - --color-accent-c: 0.22; - --color-accent-l: 58%; - --color-warning-h: 45; - --color-warning-c: 0.22; - --color-success-h: 120; - --color-error-h: 4; +[data-theme-preset="midnight"] { + --color-accent-h: 300; + --color-accent-c: 0.15; + --color-accent-l: 52%; + --color-warning-h: 50; + --color-warning-c: 0.18; + --color-success-h: 135; + --color-error-h: 5; + --color-error-l: 62%; + --color-error-c: 0.22; + --color-info-h: 195; + --color-info-c: 0.12; - --bg-base: oklch(95% 0.02 80); - --bg-elevated: oklch(97% 0.015 80 / 0.95); - --bg-hover: oklch(91% 0.03 80); - --bg-disabled: oklch(90% 0.02 80); + --bg-base: oklch(96% 0.01 255); + --bg-elevated: oklch(98% 0.008 255 / 0.95); + --bg-hover: oklch(93% 0.02 255); + --bg-disabled: oklch(92% 0.01 255); - --text-primary: oklch(28% 0.03 55); - --text-secondary: oklch(48% 0.03 55); - --text-inverse: oklch(95% 0.02 80); + --text-primary: oklch(22% 0.03 260); + --text-secondary: oklch(48% 0.03 260); + --text-inverse: oklch(97% 0.01 255); - --surface-base: oklch(96% 0.015 80); - --surface-elevated: oklch(97% 0.015 80 / 0.95); - --surface-hover: oklch(91% 0.03 80); + --surface-base: oklch(97% 0.01 255); + --surface-elevated: oklch(98% 0.008 255 / 0.95); + --surface-hover: oklch(93% 0.02 255); --surface-subtle: oklch(0% 0 0 / 0.03); - --border-base: oklch(78% 0.04 75); - --border-subtle: oklch(78% 0.04 75 / 0.45); + --border-base: oklch(80% 0.03 255); + --border-subtle: oklch(80% 0.03 255 / 0.45); - --favorite-color: oklch(72% 0.16 75); - --favorite-glow: oklch(72% 0.16 75 / 0.5); + --favorite-color: oklch(72% 0.16 85); + --favorite-glow: oklch(72% 0.16 85 / 0.5); } -[data-theme="dark"][data-theme-preset="gruvbox"] { - --color-accent-h: 25; - --color-accent-c: 0.22; - --color-accent-l: 62%; - --color-warning-h: 45; - --color-warning-c: 0.22; - --color-success-h: 120; - --color-error-h: 4; +[data-theme="dark"][data-theme-preset="midnight"] { + --color-accent-h: 300; + --color-accent-c: 0.14; + --color-accent-l: 68%; + --color-warning-h: 50; + --color-warning-c: 0.18; + --color-success-h: 135; + --color-error-h: 5; + --color-error-l: 65%; + --color-error-c: 0.22; + --color-info-h: 195; + --color-info-c: 0.12; - --bg-base: oklch(22% 0.02 55); - --bg-elevated: oklch(26% 0.025 55 / 0.98); - --bg-hover: oklch(32% 0.03 55); - --bg-disabled: oklch(30% 0.02 55); + --bg-base: oklch(18% 0.03 260); + --bg-elevated: oklch(22% 0.03 260 / 0.98); + --bg-hover: oklch(28% 0.03 260); + --bg-disabled: oklch(28% 0.02 260); - --text-primary: oklch(85% 0.03 75); - --text-secondary: oklch(68% 0.03 75); - --text-inverse: oklch(22% 0.02 55); + --text-primary: oklch(88% 0.02 255); + --text-secondary: oklch(68% 0.02 255); + --text-inverse: oklch(18% 0.03 260); - --surface-base: oklch(28% 0.025 55); - --surface-elevated: oklch(26% 0.025 55 / 0.98); - --surface-hover: oklch(32% 0.03 55); + --surface-base: oklch(24% 0.03 260); + --surface-elevated: oklch(22% 0.03 260 / 0.98); + --surface-hover: oklch(28% 0.03 260); --surface-subtle: oklch(100% 0 0 / 0.03); - --border-base: oklch(38% 0.03 55); - --border-subtle: oklch(85% 0.03 75 / 0.15); + --border-base: oklch(36% 0.03 260); + --border-subtle: oklch(88% 0.02 255 / 0.15); - --favorite-color: oklch(78% 0.16 75); - --favorite-glow: oklch(78% 0.16 75 / 0.5); + --favorite-color: oklch(78% 0.16 85); + --favorite-glow: oklch(78% 0.16 85 / 0.5); } /* ── Preset: Monokai ───────────────────────────────────────── */ @@ -249,7 +265,10 @@ --color-warning-h: 50; --color-warning-c: 0.22; --color-success-h: 140; + --color-error-l: 60%; + --color-error-c: 0.22; --color-error-h: 340; + --color-info-h: 250; --bg-base: oklch(96% 0.01 80); --bg-elevated: oklch(98% 0.005 80 / 0.95); @@ -279,7 +298,10 @@ --color-warning-h: 50; --color-warning-c: 0.22; --color-success-h: 140; + --color-error-l: 65%; + --color-error-c: 0.22; --color-error-h: 340; + --color-info-h: 250; --bg-base: oklch(18% 0.02 100); --bg-elevated: oklch(22% 0.02 100 / 0.98); @@ -311,7 +333,10 @@ --color-warning-h: 45; --color-warning-c: 0.22; --color-success-h: 135; + --color-error-l: 62%; + --color-error-c: 0.22; --color-error-h: 350; + --color-info-h: 195; --bg-base: oklch(96% 0.01 290); --bg-elevated: oklch(98% 0.008 290 / 0.95); @@ -341,7 +366,10 @@ --color-warning-h: 45; --color-warning-c: 0.22; --color-success-h: 135; + --color-error-l: 65%; + --color-error-c: 0.22; --color-error-h: 350; + --color-info-h: 195; --bg-base: oklch(18% 0.04 290); --bg-elevated: oklch(22% 0.04 290 / 0.98); @@ -373,7 +401,12 @@ --color-warning-h: 45; --color-warning-c: 0.20; --color-success-h: 68; - --color-error-h: 25; + --color-error-l: 62%; + --color-error-c: 0.22; + --color-error-h: 5; + --color-info-h: 220; + --color-info-c: 0.16; + --color-info-l: 68%; --bg-base: oklch(95% 0.03 85); --bg-elevated: oklch(97% 0.025 85 / 0.95); @@ -403,7 +436,12 @@ --color-warning-h: 45; --color-warning-c: 0.20; --color-success-h: 68; - --color-error-h: 25; + --color-error-l: 65%; + --color-error-c: 0.22; + --color-error-h: 5; + --color-info-h: 220; + --color-info-c: 0.16; + --color-info-l: 68%; --bg-base: oklch(18% 0.05 200); --bg-elevated: oklch(22% 0.05 200 / 0.98); @@ -411,7 +449,7 @@ --bg-disabled: oklch(28% 0.04 200); --text-primary: oklch(72% 0.03 85); - --text-secondary: oklch(58% 0.03 85); + --text-secondary: oklch(62% 0.03 85); --text-inverse: oklch(18% 0.05 200); --surface-base: oklch(24% 0.05 200); diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index 20b6d60b..d53b43fd 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -198,15 +198,20 @@ export function restoreFolderFilter() { } const CYCLE_ORDER = ['auto', 'light', 'dark']; -const PRESET_NAMES = ['default', 'nord', 'gruvbox', 'monokai', 'dracula', 'solarized']; +const PRESET_NAMES = ['default', 'nord', 'midnight', 'monokai', 'dracula', 'solarized']; export { CYCLE_ORDER, PRESET_NAMES }; export function initTheme() { const savedTheme = getStorageItem('theme') || 'auto'; - const savedPreset = getStorageItem('theme_preset') || 'default'; + // Migrate deprecated presets + let savedPreset = getStorageItem('theme_preset'); + if (savedPreset === 'gruvbox') { + savedPreset = 'midnight'; + setStorageItem('theme_preset', 'midnight'); + } applyTheme(savedTheme); - applyPreset(savedPreset); + applyPreset(savedPreset || 'default'); window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { const currentTheme = getStorageItem('theme') || 'auto'; diff --git a/templates/components/header.html b/templates/components/header.html index 35a499bd..7f2cd749 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -102,9 +102,9 @@ {{ t('header.theme.nord') }} -