From db892ae285c0545e9c1b9fb652601f8d09aeb3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Fri, 10 Apr 2026 07:19:16 +0200 Subject: [PATCH] fix(ui): move all :is(.dark) component class rules outside @layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rules inside @layer components lose to unlayered styles in the CSS cascade, causing dark mode overrides to be silently ignored. Move ALL :is(.dark) rules for app-surface, app-surface-strong, app-toolbar, app-input, app-select, app-label, app-page-title, app-page-subtitle, app-data-table, and action classes outside @layer — the same fix that resolved app-data-table white bg. Also switch app-surface/strong from background: shorthand to separate background-color + background-image to ensure the dark surface-card base color is always applied independently of the gradient overlay. Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/app/globals.css | 191 ++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 92 deletions(-) diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css index df49d90..8c1400c 100644 --- a/apps/web/src/app/globals.css +++ b/apps/web/src/app/globals.css @@ -435,38 +435,22 @@ } @layer components { + /* Light mode only — dark overrides are ALL outside @layer to ensure they win */ + .app-surface { - @apply rounded-2xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900/90; - background: linear-gradient(145deg, #ffffff 0%, #fdfdfd 100%); + @apply rounded-2xl border border-gray-200 bg-white; + background-image: linear-gradient(145deg, #ffffff 0%, #fdfdfd 100%); --tw-shadow: 0 1px 3px rgb(var(--accent-400) / 0.06), 0 4px 16px rgb(var(--accent-400) / 0.04); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } - :is(.dark) .app-surface { - background: linear-gradient(135deg, rgba(255,255,255,0.055) 0%, rgba(255,255,255,0.018) 100%); - backdrop-filter: blur(16px); - -webkit-backdrop-filter: blur(16px); - border-color: rgba(255,255,255,0.09); - --tw-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); - box-shadow: var(--tw-shadow); - } - .app-surface-strong { - @apply rounded-3xl border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900; - background: linear-gradient(145deg, #ffffff 0%, #fdfdfd 100%); + @apply rounded-3xl border border-gray-200 bg-white; + background-image: linear-gradient(145deg, #ffffff 0%, #fdfdfd 100%); --tw-shadow: 0 2px 8px rgb(var(--accent-400) / 0.06), 0 8px 24px rgb(var(--accent-400) / 0.04); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } - :is(.dark) .app-surface-strong { - background: linear-gradient(135deg, rgba(255,255,255,0.065) 0%, rgba(255,255,255,0.022) 100%); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border-color: rgba(255,255,255,0.10); - --tw-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); - box-shadow: var(--tw-shadow); - } - .app-toolbar { @apply sticky top-0 z-10 rounded-2xl border p-4; background: rgb(255 255 255 / 0.85); @@ -476,14 +460,6 @@ box-shadow: 0 1px 3px rgb(var(--accent-400) / 0.06); } - :is(.dark) .app-toolbar { - background: rgb(16 17 19 / 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); - } - .app-input { @apply w-full rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition; } @@ -494,41 +470,15 @@ outline: none; } - :is(.dark) .app-input { - background-color: rgb(var(--surface-input)); - border-color: rgb(var(--border-input)); - color: rgb(var(--text-primary)); - } - - :is(.dark) .app-input:focus { - border-color: rgb(var(--accent-400)); - box-shadow: 0 0 0 3px rgba(var(--accent-400), 0.20), inset 0 0 8px rgba(var(--accent-400), 0.08); - } - .app-select { @apply rounded-xl border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 outline-none transition; @apply focus:border-brand-500 focus:ring-4 focus:ring-brand-100/80; } - :is(.dark) .app-select { - background-color: rgb(var(--surface-input)); - border-color: rgb(var(--border-input)); - color: rgb(var(--text-primary)); - } - - :is(.dark) .app-select:focus { - border-color: rgb(var(--accent-400)); - box-shadow: 0 0 0 3px rgb(var(--accent-400) / 0.20); - } - .app-label { @apply mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-500; } - :is(.dark) .app-label { - color: rgb(var(--text-very-muted)); - } - .app-page { @apply p-6 md:p-8; } @@ -541,32 +491,14 @@ @apply font-display text-3xl font-semibold text-gray-900; } - :is(.dark) .app-page-title { - background: linear-gradient(135deg, rgb(var(--accent-200)) 0%, rgb(var(--accent-100)) 50%, rgb(237 242 247) 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - } - .app-page-subtitle { @apply text-sm text-gray-500; } - :is(.dark) .app-page-subtitle { - color: rgb(var(--text-muted)); - } - .app-data-table { @apply overflow-hidden rounded-2xl border border-gray-200 bg-white shadow-sm; } - /* Explicit dark rule — @apply dark: inside @layer components is unreliable */ - :is(.dark) .app-data-table { - background-color: rgb(var(--surface-card)); - border-color: rgb(var(--border-subtle)); - box-shadow: none; - } - .app-data-table table { @apply min-w-full text-sm; } @@ -575,19 +507,11 @@ @apply bg-gray-50/90; } - :is(.dark) .app-data-table thead tr { - background: rgba(255,255,255,0.04); - } - .app-data-table th { @apply text-[11px] font-semibold uppercase tracking-[0.16em] text-gray-500; } - :is(.dark) .app-data-table th { - color: rgb(var(--text-very-muted)); - } - - /* ─── Semantic action classes ─────────────────────────────────────────── */ + /* ─── Semantic action classes (light mode only) ──────────────────────── */ .app-action-edit { @apply text-xs font-medium cursor-pointer transition-colors; @@ -597,8 +521,6 @@ color: rgb(37 99 235); text-decoration: underline; } - :is(.dark) .app-action-edit { color: rgb(96 165 250); } - :is(.dark) .app-action-edit:hover { color: rgb(147 197 253); } .app-action-delete { @apply text-xs font-medium cursor-pointer transition-colors; @@ -608,8 +530,6 @@ color: rgb(185 28 28); text-decoration: underline; } - :is(.dark) .app-action-delete { color: rgb(248 113 113); } - :is(.dark) .app-action-delete:hover { color: rgb(252 165 165); } .app-action-danger-btn { @apply rounded px-2 py-1 text-sm font-medium cursor-pointer transition-colors; @@ -619,11 +539,98 @@ color: rgb(185 28 28); background-color: rgb(254 242 242); } - :is(.dark) .app-action-danger-btn { color: rgb(248 113 113); } - :is(.dark) .app-action-danger-btn:hover { - color: rgb(252 165 165); - background-color: rgb(127 29 29 / 0.2); - } +} + +/* ─── Dark overrides for component classes — ALL outside @layer ────────────── + Rules inside @layer components are lower priority than unlayered styles. + Placing :is(.dark) rules here (unlayered) ensures they always win. + ─────────────────────────────────────────────────────────────────────────── */ + +: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%); + 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); +} + +: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%); + 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); +} + +:is(.dark) .app-toolbar { + background: rgb(16 17 19 / 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); +} + +:is(.dark) .app-input { + background-color: rgb(var(--surface-input)); + border-color: rgb(var(--border-input)); + color: rgb(var(--text-primary)); +} + +:is(.dark) .app-input:focus { + border-color: rgb(var(--accent-400)); + box-shadow: 0 0 0 3px rgba(var(--accent-400), 0.20), inset 0 0 8px rgba(var(--accent-400), 0.08); +} + +:is(.dark) .app-select { + background-color: rgb(var(--surface-input)); + border-color: rgb(var(--border-input)); + color: rgb(var(--text-primary)); +} + +:is(.dark) .app-select:focus { + border-color: rgb(var(--accent-400)); + box-shadow: 0 0 0 3px rgb(var(--accent-400) / 0.20); +} + +:is(.dark) .app-label { + color: rgb(var(--text-very-muted)); +} + +:is(.dark) .app-page-title { + background-image: linear-gradient(135deg, rgb(var(--accent-200)) 0%, rgb(var(--accent-100)) 50%, rgb(237 242 247) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +:is(.dark) .app-page-subtitle { + color: rgb(var(--text-muted)); +} + +:is(.dark) .app-data-table { + background-color: rgb(var(--surface-card)); + border-color: rgb(var(--border-subtle)); + box-shadow: none; +} + +:is(.dark) .app-data-table thead tr { + background: rgba(255,255,255,0.04); +} + +:is(.dark) .app-data-table th { + color: rgb(var(--text-very-muted)); +} + +:is(.dark) .app-action-edit { color: rgb(96 165 250); } +:is(.dark) .app-action-edit:hover { color: rgb(147 197 253); } +:is(.dark) .app-action-delete { color: rgb(248 113 113); } +:is(.dark) .app-action-delete:hover { color: rgb(252 165 165); } +:is(.dark) .app-action-danger-btn { color: rgb(248 113 113); } +:is(.dark) .app-action-danger-btn:hover { + color: rgb(252 165 165); + background-color: rgb(127 29 29 / 0.2); } /* ─── Timeline utilities (unchanged) ────────────────────────────────────── */