diff --git a/DeepDrftPublic.Client/Layout/MainLayout.razor b/DeepDrftPublic.Client/Layout/MainLayout.razor index d201029..38dca39 100644 --- a/DeepDrftPublic.Client/Layout/MainLayout.razor +++ b/DeepDrftPublic.Client/Layout/MainLayout.razor @@ -42,6 +42,7 @@ private string _audioPlayerClass = "minimized"; private const string DarkModeKey = "darkMode"; private bool _isDarkMode = false; + private bool? _lastAppliedDarkMode = null; private PersistingComponentStateSubscription _persistingSubscription; [Inject] public required PersistentComponentState PersistentState { get; set; } @@ -70,10 +71,18 @@ // Sync dark mode class on so portaled MudBlazor elements (popovers, menus, selects) // inherit --deepdrft-popover-surface from body.deepdrft-theme-dark rather than from :root only. // Popovers portal outside the ThemeWrapperClass div, so only a body-level class can reach them. + // Gated: only fires on first render or when _isDarkMode actually changes, to avoid redundant + // JS calls on unrelated re-renders (e.g. audio player minimize/expand). protected override async Task OnAfterRenderAsync(bool firstRender) { - await JS.InvokeVoidAsync("eval", - $"document.body.classList.toggle('deepdrft-theme-dark', {_isDarkMode.ToString().ToLower()})"); + await base.OnAfterRenderAsync(firstRender); + + if (firstRender || _isDarkMode != _lastAppliedDarkMode) + { + _lastAppliedDarkMode = _isDarkMode; + await JS.InvokeVoidAsync("eval", + $"document.body.classList.toggle('deepdrft-theme-dark', {_isDarkMode.ToString().ToLower()})"); + } } // Theme wrapper class for CSS targeting diff --git a/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css b/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css index dfcf604..3a08ea4 100644 --- a/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css +++ b/DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css @@ -87,10 +87,12 @@ /* Popover surface (Phase 18). Default MudBlazor popovers (selects/menus/tooltips/share body) bind this. Light uses a very subtle navy wash (4%) — near the page background but just perceptibly off-white so the popover reads as an elevated surface. Dark uses a - bluer navy (colour-mix of navy-mid + green-accent at 20%), defined in the - body.deepdrft-theme-dark block below so it reaches portaled popover content (popovers - portal to , outside the .deepdrft-theme-dark wrapper div). Bespoke dark-glass - panels (visualizer/queue/privacy) do NOT bind this — they keep --deepdrft-panel-ground. */ + bluer navy (colour-mix of navy-mid + green-accent at 20%), defined once in + --deepdrft-popover-surface-dark below and referenced by both the .deepdrft-theme-dark + wrapper block and the body.deepdrft-theme-dark block so portaled popover content (which + portals to , outside the wrapper div) is also reached. Bespoke dark-glass panels + (visualizer/queue/privacy) do NOT bind this — they keep --deepdrft-panel-ground. */ + --deepdrft-popover-surface-dark: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%); --deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy) 4%, var(--deepdrft-white)); /* Fixed-nav height — single source of truth shared by the frosted-glass nav @@ -167,17 +169,16 @@ --deepdrft-play-chip-soft: color-mix(in srgb, var(--deepdrft-green-accent) 30%, transparent); /* Popover surface (Phase 18) — within .deepdrft-theme-dark wrapper this value applies to - non-portaled elements only. Portaled MudBlazor popovers live at level; the - body.deepdrft-theme-dark block below is the authoritative dark value for those. Keep - this in sync with that block for non-portaled surfaces (drawers, inline menus). */ - --deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%); + non-portaled elements only (drawers, inline menus). Portaled MudBlazor popovers live at + level; the body.deepdrft-theme-dark block below uses the same source token. */ + --deepdrft-popover-surface: var(--deepdrft-popover-surface-dark); } /* Portal-scope dark popover surface. MudBlazor popovers (selects, menus, share body) portal to , placing them outside the .deepdrft-theme-dark wrapper div. MainLayout.razor syncs deepdrft-theme-dark onto via JS after each render, so this selector reaches portaled - content. The value mirrors the .deepdrft-theme-dark block above — bluer navy + content. Resolved from --deepdrft-popover-surface-dark (defined in :root above) — bluer navy (navy-mid + 20% green-accent tint) rather than the pure charcoal #162437. */ body.deepdrft-theme-dark { - --deepdrft-popover-surface: color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%); + --deepdrft-popover-surface: var(--deepdrft-popover-surface-dark); }