fix(ui): neutralise dark theme — eliminate blue-shifted grays across all surfaces

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 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 09:20:38 +02:00
parent db892ae285
commit 2a91257e69
+183 -84
View File
@@ -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 ─────────────────────────── */