From 2a91257e6980640470130d051915f6ecb25e2ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Fri, 10 Apr 2026 09:20:38 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20neutralise=20dark=20theme=20?= =?UTF-8?q?=E2=80=94=20eliminate=20blue-shifted=20grays=20across=20all=20s?= =?UTF-8?q?urfaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace blue-shifted CSS variable values with balanced neutral RGB, add comprehensive dark-mode overrides for bg-gray-*, border-gray-*, text-gray-*, and their dark: variant forms. Remove light-mode text/border overrides that leaked into both modes. Replace hardcoded rgba(255,255,255,...) in component classes with CSS variable references. Merge duplicate fadeSlideIn keyframe into fadeSlideUp. Change .app-data-table overflow to clip for sticky compat. Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/app/globals.css | 267 ++++++++++++++++++++++++----------- 1 file changed, 183 insertions(+), 84 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index 8c1400c..b36645d 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -108,16 +108,16 @@ .dark { color-scheme: dark; - --surface-page: 10 10 12; - --surface-card: 16 17 19; - --surface-elevated: 22 23 26; - --surface-input: 13 14 16; - --border-subtle: 36 38 44; - --border-input: 54 57 66; - --text-primary: 237 242 247; - --text-secondary: 196 207 223; - --text-muted: 147 161 185; - --text-very-muted: 118 133 161; + --surface-page: 10 10 10; + --surface-card: 17 17 17; + --surface-elevated: 24 24 24; + --surface-input: 14 14 14; + --border-subtle: 38 38 38; + --border-input: 58 58 58; + --text-primary: 240 241 243; + --text-secondary: 205 207 210; + --text-muted: 158 160 164; + --text-very-muted: 130 132 136; --shadow-soft: 0 0 0 / 0.45; --shadow-strong: 0 0 0 / 0.75; } @@ -143,7 +143,7 @@ background-image: radial-gradient(ellipse 80% 50% at 50% -10%, rgba(var(--accent-400), 0.07) 0%, transparent 60%), radial-gradient(circle at top left, rgb(var(--accent-600) / 0.16), transparent 26rem), - linear-gradient(180deg, rgb(15 23 42 / 0.35), transparent 28rem); + linear-gradient(180deg, rgb(10 10 12 / 0.35), transparent 28rem); } h1, @@ -220,6 +220,26 @@ background-color: rgb(var(--surface-elevated)) !important; } +.dark .bg-gray-200 { + background-color: rgb(var(--surface-elevated)) !important; +} + +.dark .bg-gray-300 { + background-color: rgb(var(--border-subtle)) !important; +} + +.dark .bg-gray-400 { + background-color: rgb(var(--border-input)) !important; +} + +.dark .bg-gray-500 { + background-color: rgb(80 80 80) !important; +} + +.dark .bg-gray-600 { + background-color: rgb(var(--border-input)) !important; +} + .dark .border-gray-100 { border-color: rgb(var(--border-subtle)) !important; } @@ -256,31 +276,11 @@ color: rgb(var(--text-very-muted)) !important; } -.text-gray-900 { - color: rgb(var(--text-primary)) !important; -} - -.text-gray-800, -.text-gray-700 { - color: rgb(var(--text-secondary)) !important; -} - -.text-gray-600, -.text-gray-500 { - color: rgb(var(--text-muted)) !important; -} - -.text-gray-400 { - color: rgb(var(--text-very-muted)) !important; -} - -.border-gray-200 { - border-color: rgb(var(--border-subtle)) !important; -} - -.border-gray-300 { - border-color: rgb(var(--border-input)) !important; -} +/* Dark variant text overrides — catches className="dark:text-gray-*" */ +.dark .dark\:text-gray-100 { color: rgb(var(--text-primary)) !important; } +.dark .dark\:text-gray-200 { color: rgb(var(--text-secondary)) !important; } +.dark .dark\:text-gray-300 { color: rgb(var(--text-muted)) !important; } +.dark .dark\:text-gray-400 { color: rgb(var(--text-very-muted)) !important; } .dark input, .dark select, @@ -295,15 +295,13 @@ color: rgb(var(--text-muted)); } -/* Table alternating / hover */ -.dark .hover\:bg-gray-50:hover { +/* Table row / interactive hover */ +.dark .hover\:bg-gray-50:hover, +.dark .hover\:bg-gray-100:hover, +.dark .hover\:bg-gray-200:hover { background-color: rgb(var(--surface-elevated)) !important; } -.dark .divide-gray-100 > * + * { - border-color: rgb(var(--border-subtle)) !important; -} - /* Status badge adjustments in dark mode - keep them readable */ .dark .bg-green-100 { background-color: rgb(6 78 59 / 0.4) !important; @@ -332,12 +330,6 @@ .dark .text-red-700 { color: rgb(248 113 113) !important; } -.dark .bg-gray-100 { - background-color: rgb(var(--surface-elevated)) !important; -} -.dark .text-gray-700 { - color: rgb(var(--text-secondary)) !important; -} .dark .bg-purple-100 { background-color: rgb(76 29 149 / 0.4) !important; } @@ -402,24 +394,141 @@ color: rgb(var(--text-primary)); } -/* Divide override for gray-50 (ChargeabilityWidget sticky headers) */ -.dark .divide-gray-50 > * + * { border-color: rgb(var(--border-subtle)) !important; } +/* Divide overrides */ +.dark .divide-gray-50 > * + *, +.dark .divide-gray-100 > * + *, +.dark .dark\:divide-gray-800 > * + * { border-color: rgb(var(--border-subtle)) !important; } -/* Slate-* normalization — map to CSS variable surfaces */ -.dark .bg-slate-700, -.dark .bg-slate-800 { +/* ═══════════════════════════════════════════════════════════════════════════ + COMPREHENSIVE DARK SURFACE NORMALIZATION + Tailwind's gray-700/800/900 and slate-* palette is blue-shifted. + In dark mode we remap all of these to our neutral CSS variable surfaces. + + Tailwind v3 with darkMode:"class" generates selectors like: + .dark .dark\:bg-gray-800 { background-color: rgb(31 41 55 / ...) } + We override them here with neutral surfaces + !important. + + Two forms per class: + 1. Plain — matches className="bg-gray-800" (no variant) + 2. dark\: — matches className="dark:bg-gray-800" (Tailwind dark variant) + ═══════════════════════════════════════════════════════════════════════════ */ + +/* ── Light grays: gray-50 through gray-200 → elevated surface ──────────── */ +.dark .dark\:bg-gray-50, +.dark .dark\:bg-gray-100, +.dark .dark\:bg-gray-200 { background-color: rgb(var(--surface-elevated)) !important; } -.dark .bg-slate-900, -.dark .bg-gray-900 { + +/* ── Mid grays: gray-300 through gray-600 → border/mid tones ──────────── */ +.dark .dark\:bg-gray-300 { + background-color: rgb(var(--border-subtle)) !important; +} +.dark .dark\:bg-gray-400, +.dark .dark\:bg-gray-600 { + background-color: rgb(var(--border-input)) !important; +} +.dark .dark\:bg-gray-500 { + background-color: rgb(80 80 80) !important; +} + +/* ── Elevated surface: gray-700, gray-800, slate-700, slate-800 ─────────── */ +.dark .bg-gray-700, .dark .dark\:bg-gray-700, +.dark .bg-gray-800, .dark .dark\:bg-gray-800, +.dark .bg-slate-700, .dark .dark\:bg-slate-700, +.dark .bg-slate-800, .dark .dark\:bg-slate-800 { + background-color: rgb(var(--surface-elevated)) !important; +} + +/* ── Card surface: gray-900, slate-900, slate-950 ───────────────────────── */ +.dark .bg-gray-900, .dark .dark\:bg-gray-900, +.dark .bg-slate-900, .dark .dark\:bg-slate-900, +.dark .bg-slate-950, .dark .dark\:bg-slate-950 { background-color: rgb(var(--surface-card)) !important; } -.dark .border-slate-800, -.dark .border-slate-700 { + +/* ── Opacity variants: elevated surface (/95 → /20) ────────────────────── */ +.dark .bg-gray-700\/95, .dark .dark\:bg-gray-700\/95, +.dark .bg-gray-700\/80, .dark .dark\:bg-gray-700\/80, +.dark .bg-gray-700\/70, .dark .dark\:bg-gray-700\/70, +.dark .bg-gray-700\/60, .dark .dark\:bg-gray-700\/60, +.dark .bg-gray-700\/50, .dark .dark\:bg-gray-700\/50, +.dark .bg-gray-700\/40, .dark .dark\:bg-gray-700\/40, +.dark .bg-gray-800\/95, .dark .dark\:bg-gray-800\/95, +.dark .bg-gray-800\/80, .dark .dark\:bg-gray-800\/80, +.dark .bg-gray-800\/70, .dark .dark\:bg-gray-800\/70, +.dark .bg-gray-800\/60, .dark .dark\:bg-gray-800\/60, +.dark .bg-gray-800\/50, .dark .dark\:bg-gray-800\/50, +.dark .bg-gray-800\/40, .dark .dark\:bg-gray-800\/40, +.dark .bg-gray-800\/30, .dark .dark\:bg-gray-800\/30, +.dark .bg-slate-700\/95, .dark .dark\:bg-slate-700\/95, +.dark .bg-slate-700\/80, .dark .dark\:bg-slate-700\/80, +.dark .bg-slate-700\/70, .dark .dark\:bg-slate-700\/70, +.dark .bg-slate-700\/60, .dark .dark\:bg-slate-700\/60, +.dark .bg-slate-700\/50, .dark .dark\:bg-slate-700\/50, +.dark .bg-slate-800\/95, .dark .dark\:bg-slate-800\/95, +.dark .bg-slate-800\/80, .dark .dark\:bg-slate-800\/80, +.dark .bg-slate-800\/70, .dark .dark\:bg-slate-800\/70, +.dark .bg-slate-800\/60, .dark .dark\:bg-slate-800\/60, +.dark .bg-slate-800\/50, .dark .dark\:bg-slate-800\/50 { + background-color: rgb(var(--surface-elevated) / 0.9) !important; +} + +/* ── Opacity variants: card surface (/95 → /20) ─────────────────────────── */ +.dark .bg-gray-900\/95, .dark .dark\:bg-gray-900\/95, +.dark .bg-gray-900\/70, .dark .dark\:bg-gray-900\/70, +.dark .bg-gray-900\/60, .dark .dark\:bg-gray-900\/60, +.dark .bg-gray-900\/50, .dark .dark\:bg-gray-900\/50, +.dark .bg-gray-900\/40, .dark .dark\:bg-gray-900\/40, +.dark .bg-gray-900\/30, .dark .dark\:bg-gray-900\/30, +.dark .bg-gray-900\/25, .dark .dark\:bg-gray-900\/25, +.dark .bg-gray-900\/20, .dark .dark\:bg-gray-900\/20, +.dark .bg-slate-900\/95, .dark .dark\:bg-slate-900\/95, +.dark .bg-slate-900\/70, .dark .dark\:bg-slate-900\/70, +.dark .bg-slate-900\/60, .dark .dark\:bg-slate-900\/60, +.dark .bg-slate-900\/50, .dark .dark\:bg-slate-900\/50, +.dark .bg-slate-900\/40, .dark .dark\:bg-slate-900\/40, +.dark .bg-slate-900\/30, .dark .dark\:bg-slate-900\/30, +.dark .bg-slate-950\/95, .dark .dark\:bg-slate-950\/95, +.dark .bg-slate-950\/70, .dark .dark\:bg-slate-950\/70, +.dark .bg-slate-950\/60, .dark .dark\:bg-slate-950\/60, +.dark .bg-slate-950\/40, .dark .dark\:bg-slate-950\/40 { + background-color: rgb(var(--surface-card) / 0.9) !important; +} + +/* ── Hover state overrides ──────────────────────────────────────────────── */ +.dark .hover\:bg-gray-700:hover, .dark .dark\:hover\:bg-gray-700:hover, +.dark .hover\:bg-gray-800:hover, .dark .dark\:hover\:bg-gray-800:hover, +.dark .hover\:bg-slate-700:hover, .dark .dark\:hover\:bg-slate-700:hover, +.dark .hover\:bg-slate-800:hover, .dark .dark\:hover\:bg-slate-800:hover { + background-color: rgb(var(--surface-elevated)) !important; +} +.dark .hover\:bg-gray-900:hover, .dark .dark\:hover\:bg-gray-900:hover, +.dark .hover\:bg-slate-900:hover, .dark .dark\:hover\:bg-slate-900:hover { + background-color: rgb(var(--surface-card)) !important; +} + +/* ── Hover opacity variants ────────────────────────────────────────────── */ +.dark .dark\:hover\:bg-gray-800\/50:hover, +.dark .dark\:hover\:bg-gray-800\/30:hover, +.dark .dark\:hover\:bg-gray-700\/50:hover { + background-color: rgb(var(--surface-elevated) / 0.5) !important; +} + +/* ── Border normalization ───────────────────────────────────────────────── */ +.dark .border-gray-600, .dark .dark\:border-gray-600, +.dark .border-gray-700, .dark .dark\:border-gray-700, +.dark .border-gray-800, .dark .dark\:border-gray-800, +.dark .border-slate-600, .dark .dark\:border-slate-600, +.dark .border-slate-700, .dark .dark\:border-slate-700, +.dark .border-slate-800, .dark .dark\:border-slate-800 { border-color: rgb(var(--border-subtle)) !important; } -.dark .hover\:bg-slate-800:hover { - background-color: rgb(var(--surface-elevated)) !important; + +/* Border opacity variants */ +.dark .dark\:border-gray-700\/60, +.dark .dark\:border-gray-700\/40 { + border-color: rgb(var(--border-subtle) / 0.6) !important; } /* Sidebar panel — overrides the hardcoded hex values in AppShell */ @@ -496,7 +605,8 @@ } .app-data-table { - @apply overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm; + @apply rounded-2xl border border-gray-200 bg-white shadow-sm; + overflow: clip; } .app-data-table table { @@ -548,28 +658,28 @@ :is(.dark) .app-surface { background-color: rgb(var(--surface-card)); - background-image: linear-gradient(135deg, rgba(255,255,255,0.045) 0%, rgba(255,255,255,0.015) 100%); + background-image: linear-gradient(135deg, rgb(var(--text-primary) / 0.045) 0%, rgb(var(--text-primary) / 0.015) 100%); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); - border-color: rgba(255,255,255,0.09); - box-shadow: 0 2px 8px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.05), inset 0 1px 0 rgba(255,255,255,0.07); + border-color: rgb(var(--border-subtle)); + box-shadow: 0 2px 8px rgb(0 0 0 / 0.4), 0 0 0 1px rgb(var(--border-subtle) / 0.5), inset 0 1px 0 rgb(var(--text-primary) / 0.07); } :is(.dark) .app-surface-strong { background-color: rgb(var(--surface-card)); - background-image: linear-gradient(135deg, rgba(255,255,255,0.055) 0%, rgba(255,255,255,0.018) 100%); + background-image: linear-gradient(135deg, rgb(var(--text-primary) / 0.055) 0%, rgb(var(--text-primary) / 0.018) 100%); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); - border-color: rgba(255,255,255,0.10); - box-shadow: 0 4px 16px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.06), inset 0 1px 0 rgba(255,255,255,0.08); + border-color: rgb(var(--border-subtle)); + box-shadow: 0 4px 16px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(var(--border-subtle) / 0.6), inset 0 1px 0 rgb(var(--text-primary) / 0.08); } :is(.dark) .app-toolbar { - background: rgb(16 17 19 / 0.82); + background: rgb(var(--surface-card) / 0.82); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); - border-color: rgba(255,255,255,0.08); - box-shadow: 0 1px 0 rgba(255,255,255,0.04); + border-color: rgb(var(--border-subtle) / 0.6); + box-shadow: 0 1px 0 rgb(var(--text-primary) / 0.04); } :is(.dark) .app-input { @@ -616,7 +726,7 @@ } :is(.dark) .app-data-table thead tr { - background: rgba(255,255,255,0.04); + background-color: rgb(var(--surface-elevated)); } :is(.dark) .app-data-table th { @@ -716,25 +826,14 @@ } :is(.dark) .shimmer-skeleton { - background: linear-gradient(90deg, rgba(255,255,255,0.04) 0%, rgba(255,255,255,0.09) 50%, rgba(255,255,255,0.04) 100%); + background: linear-gradient(90deg, rgb(var(--text-primary) / 0.04) 0%, rgb(var(--text-primary) / 0.09) 50%, rgb(var(--text-primary) / 0.04) 100%); background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite; } -/* ─── Table row stagger entrance ─────────────────────────────────────────── */ -@keyframes fadeSlideIn { - from { - opacity: 0; - transform: translateY(6px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - +/* ─── Table row stagger entrance (reuses fadeSlideUp keyframes) ──────────── */ .animate-row-enter { - animation: fadeSlideIn 0.12s ease-out both; + animation: fadeSlideUp 0.12s ease-out both; } /* ─── Subtle hover lift for cards and table rows ─────────────────────────── */