docs: record popover-surface retune and portaled-popover body-class bridge
Note the 4%/bluer-navy --deepdrft-popover-surface values, the new --deepdrft-popover-surface-dark source token, the theme TS interop module, and the <body>-class bridge in CLAUDE.md; log Phase 18 Wave 4 in COMPLETED.md.
This commit is contained in:
@@ -80,14 +80,15 @@ Keep this seam clean — it is the most architecturally load-bearing part of the
|
||||
- Dark mode toggles via cookie (`darkMode`, 365 days). Client-side via JS interop.
|
||||
- During server prerender, `DarkModeService` (in `DeepDrftPublic`) reads the cookie and seeds `DarkModeSettings.IsDarkMode`, which carries into WASM render via `PersistentComponentState`. Avoids "wrong theme flash" on initial paint.
|
||||
- `DarkModeSettings` lives in `DeepDrftPublic.Client.Common` (consumed by both server prerender and client components).
|
||||
- **Theme-aware token layer:** `DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css` defines two kinds of CSS custom properties. *Source tokens* (`--deepdrft-navy`, `--deepdrft-white`, `--deepdrft-green-accent`, etc.) are brand constants — identical in `:root` and `.deepdrft-theme-dark`. *Theme-aware aliases* are defined in both blocks and flip when the theme wrapper class changes. Component and page CSS must bind the **alias**, not the source token, so neutral surfaces invert for free. Current alias families: `--deepdrft-page-surface`/`-text`/`-text-muted` (neutral page backgrounds and text), `--deepdrft-play-chip`/`-glyph`/`-chip-soft` (play-state icon chip and glyph), `--deepdrft-popover-surface` (default MudBlazor popover background). Bespoke dark-glass panels (visualizer/queue/privacy) bind `--deepdrft-panel-ground` directly and are intentionally exempt from the neutral-surface alias.
|
||||
- **Theme-aware token layer:** `DeepDrftShared.Client/wwwroot/styles/deepdrft-tokens.css` defines two kinds of CSS custom properties. *Source tokens* (`--deepdrft-navy`, `--deepdrft-white`, `--deepdrft-green-accent`, etc.) are brand constants — identical in `:root` and `.deepdrft-theme-dark`. *Theme-aware aliases* are defined in both blocks and flip when the theme wrapper class changes. Component and page CSS must bind the **alias**, not the source token, so neutral surfaces invert for free. Current alias families: `--deepdrft-page-surface`/`-text`/`-text-muted` (neutral page backgrounds and text), `--deepdrft-play-chip`/`-glyph`/`-chip-soft` (play-state icon chip and glyph), `--deepdrft-popover-surface` (default MudBlazor popover background — light: `color-mix(navy 4%, white)`, a near-page-background surface; dark: references source token `--deepdrft-popover-surface-dark`, a `color-mix(navy-mid 80%, green-accent 20%)` bluer navy defined once in `:root` and referenced by both the `.deepdrft-theme-dark` wrapper block and `body.deepdrft-theme-dark` so portaled popovers are reached). Bespoke dark-glass panels (visualizer/queue/privacy) bind `--deepdrft-panel-ground` directly and are intentionally exempt from the neutral-surface alias.
|
||||
- **Portaled-popover body-class bridge:** MudBlazor popovers portal to `<body>`, outside the `.deepdrft-theme-dark` wrapper `<div>`, so the dark popover token never reached them. Fix: `MainLayout.razor` stamps `deepdrft-theme-dark` on `<body>` via the `setBodyThemeClass(isDark)` helper in `DeepDrftShared.Client/Interop/theme/theme.ts` (lazy-imported as `_content/DeepDrftShared.Client/js/theme/theme.js`). The call fires only on first render or when `_isDarkMode` actually changes (gated by `_lastAppliedDarkMode` comparison) to avoid redundant JS calls on unrelated re-renders. The `body.deepdrft-theme-dark` selector in `deepdrft-tokens.css` resolves `--deepdrft-popover-surface` from `--deepdrft-popover-surface-dark` for these portaled elements.
|
||||
- Typography: Google Fonts (Bodoni Moda, Cormorant, DM Sans). Hand-rolled gas-lamp icon (lit/unlit) lives in `DeepDrftShared.Client/Common/DDIcons.cs`.
|
||||
|
||||
### TypeScript interop, not raw JS
|
||||
|
||||
Audio interop authored in TypeScript under `DeepDrftPublic/Interop/audio/`, compiled to `wwwroot/js/audio/` via `Microsoft.TypeScript.MSBuild`. One module per responsibility (AudioContextManager, StreamDecoder, PlaybackScheduler, SpectrumAnalyzer, AudioPlayer), plus `index.ts` exposing `window.DeepDrftAudio`. `tsconfig.json` is **not** copied to output. In dev, raw `.ts` served from `/Interop/` for source-map debugging. A second interop module lives at `DeepDrftPublic/Interop/about/about-rail.ts` (IntersectionObserver for the About page active-movement rail highlight; compiled output gitignored).
|
||||
|
||||
**`DeepDrftShared.Client` also hosts TypeScript interop.** Its `tsconfig.json` maps `rootDir: "Interop"` → `outDir: "wwwroot/js"`, compiled by the same `Microsoft.TypeScript.MSBuild` package. Current modules: `Interop/parallax/parallax.ts` (parallax scroll for `ParallaxImage`) and `Interop/knob/knob.ts` (`capturePointer`/`releasePointer` for `RadialKnob`). Consumers lazy-import via the static-asset path `_content/DeepDrftShared.Client/js/<module>/<file>.js`.
|
||||
**`DeepDrftShared.Client` also hosts TypeScript interop.** Its `tsconfig.json` maps `rootDir: "Interop"` → `outDir: "wwwroot/js"`, compiled by the same `Microsoft.TypeScript.MSBuild` package. Current modules: `Interop/parallax/parallax.ts` (parallax scroll for `ParallaxImage`), `Interop/knob/knob.ts` (`capturePointer`/`releasePointer` for `RadialKnob`), and `Interop/theme/theme.ts` (`setBodyThemeClass(isDark)` — stamps/removes `deepdrft-theme-dark` on `<body>` so portaled MudBlazor elements inherit the dark popover token; consumed by `MainLayout.razor`). Consumers lazy-import via the static-asset path `_content/DeepDrftShared.Client/js/<module>/<file>.js`.
|
||||
|
||||
## Development Commands
|
||||
|
||||
|
||||
@@ -28,6 +28,18 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
|
||||
|
||||
- **Design memo:** `product-notes/theme-dark-mode-remediation.md`.
|
||||
|
||||
### Phase 18 — Wave 4 — Popover-surface retune + portaled-popover body-class bridge (landed 2026-06-20)
|
||||
|
||||
**Landed:** 2026-06-20 on dev.
|
||||
|
||||
- **What:** Follow-on retune of `--deepdrft-popover-surface` values and a root-cause fix for portaled MudBlazor popovers that were never reaching the dark token.
|
||||
|
||||
- **Why:** Wave 1–3 shipped `--deepdrft-popover-surface` light at `color-mix(navy 8%, white)` (too saturated — read as a grey slab) and dark at flat `#162437`. More importantly, MudBlazor popovers portal to `<body>`, outside the `.deepdrft-theme-dark` wrapper `<div>`, so the dark token never applied to them at all. Both needed fixing as a pair.
|
||||
|
||||
- **Shape:**
|
||||
- **Token retune (`deepdrft-tokens.css`):** Light value changed from 8% → 4% navy mix (near-page-background, clearly light). Dark value changed from `#162437` to `color-mix(in srgb, var(--deepdrft-navy-mid) 80%, var(--deepdrft-green-accent) 20%)` — a bluer navy with a slight green accent. Dark value hoisted into a new source token `--deepdrft-popover-surface-dark` (defined once in `:root`), referenced by both the `.deepdrft-theme-dark` wrapper block and a new `body.deepdrft-theme-dark` block so portaled content is reached from either selector.
|
||||
- **Portaled-popover body-class bridge (`MainLayout.razor` + new TS module):** `MainLayout.razor` now stamps/removes `deepdrft-theme-dark` on `<body>` after each render via a new `DeepDrftShared.Client/Interop/theme/theme.ts` module exporting `setBodyThemeClass(isDark: boolean)`. Lazy-imported as `_content/DeepDrftShared.Client/js/theme/theme.js`. Call is gated to fire only on first render or when `_isDarkMode` changes (`_lastAppliedDarkMode` comparison) — no redundant JS calls on unrelated re-renders. `IJSObjectReference _themeModule` is disposed in `DisposeAsync` to clean up the module reference when the circuit tears down.
|
||||
|
||||
---
|
||||
|
||||
## Phase 17 — Player-Bar Queue View: Wave 17.3 — Fixed embed panel + iframe resize (landed 2026-06-19)
|
||||
|
||||
Reference in New Issue
Block a user