fix(theme): green hero Share/Play/Queue glyphs in dark via shared .dd-accent-icon
Fold Session/Mix hero glyphs into the reusable accent-icon treatment so they reach the glyph (beating .mud-secondary-text) green-accent in both themes; drop the dead wrapper white rule and the redundant dark-only hero override. Light pixel-identical.
This commit is contained in:
@@ -82,6 +82,7 @@ Keep this seam clean — it is the most architecturally load-bearing part of the
|
||||
- `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 — 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). The bespoke glass panels (visualizer/queue/privacy) now bind their own theme-aware `--deepdrft-panel-surface`/`-text`/`-text-muted`/`-border`/`-row-hover` family: dark-glass charcoal (sourced from the `--deepdrft-panel-ground` constant) with light text in dark theme, and a light translucent glass with dark text in light theme. These tokens are re-declared in `body.deepdrft-theme-dark` because the panels are MudOverlay panels that portal to `<body>` (same portal scope as popovers); the `--deepdrft-panel-ground` source token is now consumed only via the dark `--deepdrft-panel-surface` value.
|
||||
- **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.
|
||||
- **Interactive-accent icon treatment (`.dd-accent-icon` / `.dd-accent-fill`):** one reusable rule in `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` for green-accent interactive icon affordances (Play / Share / Add-to-Queue / lava-lamp trigger), replacing the former pile of per-site dark overrides. Wrap the affordance container in `.dd-accent-icon` to colour its glyphs green-accent in both themes; add `.dd-accent-fill` when the container also holds a `Color.Secondary` filled button that must go green-accent in dark. It is a CSS class (not a palette `Color`) because no MudBlazor `Color` enum is green in both themes, and it targets `.dd-accent-icon .mud-icon-button .mud-icon-root` with `!important` (0,3,0) to beat MudBlazor's `.mud-secondary-text { …!important }` on the glyph svg. The Session/Mix release-detail hero Share/Play glyphs use this class too (already green-accent in light via `Color.Secondary`, so folding them in keeps light pixel-identical and fixes dark). The one genuinely theme-divergent affordance (gas-lamp toggle = inherited nav text in light) keeps its own dark-only rule. New green-accent icons use this class, not a new override. (Convention detail in `DeepDrftPublic.Client/CLAUDE.md`.)
|
||||
- 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
|
||||
|
||||
@@ -140,6 +140,12 @@ Component state lives in ViewModels (registered scoped in DI). Components render
|
||||
- CSS classes prefixed `deepdrft-` live in `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` (shared across server and client).
|
||||
- Custom SVG icons: `DeepDrftShared.Client/Common/DDIcons.cs` (hand-rolled gas-lamp, lava-lamp, etc. — shared across public and CMS surfaces).
|
||||
|
||||
### Interactive-accent icons (`.dd-accent-icon` / `.dd-accent-fill`)
|
||||
|
||||
Green-accent interactive icon affordances (Play / Share / Add-to-Queue / lava-lamp trigger, etc.) use a **single reusable treatment** in `deepdrft-styles.css`, not per-site dark overrides. Wrap the affordance(s) in a container carrying `.dd-accent-icon`; the rule colours the inner `.mud-icon-root` glyph green-accent (`--deepdrft-green-accent`, the brand constant — same value in both palettes) in **both** themes. Add `.dd-accent-fill` to the same container when it also holds a filled `Color.Secondary` `MudButton` whose fill must go green-accent in **dark** (dark-only — light already renders green fill + white text).
|
||||
|
||||
Two reasons this is needed and why it's a class, not a palette colour: (1) no MudBlazor `Color` enum is green in both themes (`Dark.Secondary` is off-white), so palette-only solutions can't express "green in both"; (2) MudBlazor stamps `.mud-secondary-text { color: …secondary !important }` on the glyph `<svg>`, so wrapper-level overrides never reach it — the reusable rule targets `.dd-accent-icon .mud-icon-button .mud-icon-root` (specificity 0,3,0 + `!important`) to beat it. The Session/Mix release-detail hero Share/Play glyphs use this class too: they were already green-accent in light (via `Color.Secondary` → `Light.Secondary`), so folding them in keeps light pixel-identical while fixing the dark over-image glyphs — they are not actually theme-divergent. The one genuinely theme-divergent affordance (the gas-lamp toggle = inherited nav text in light) does **not** use this class — it keeps a narrow dark-only rule. **Add new green-accent icon affordances by applying this class, not by spawning a new dark override.**
|
||||
|
||||
## Development commands
|
||||
|
||||
```bash
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</MudStack>
|
||||
@if (ShareContent is not null)
|
||||
{
|
||||
<div class="release-hero-share">
|
||||
<div class="release-hero-share dd-accent-icon">
|
||||
@ShareContent
|
||||
</div>
|
||||
}
|
||||
@@ -74,7 +74,7 @@
|
||||
</div>
|
||||
@if (PlayContent is not null)
|
||||
{
|
||||
<div class="release-hero-play">
|
||||
<div class="release-hero-play dd-accent-icon">
|
||||
@PlayContent
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -151,14 +151,10 @@
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
/* The play affordance and share button sit over a dark image — force their icon glyphs to the
|
||||
light theme color regardless of MudBlazor's Secondary palette. Both PlayStateIcon and
|
||||
SharePopover render MudIconButton / MudProgressCircular internals, so ::deep is required. */
|
||||
::deep .release-hero-play .mud-icon-button,
|
||||
::deep .release-hero-play .mud-progress-circular,
|
||||
::deep .release-hero-share .mud-icon-button {
|
||||
color: var(--deepdrft-white);
|
||||
}
|
||||
/* The play/share glyphs are coloured by the shared .dd-accent-icon treatment (green-accent in
|
||||
both themes) applied on .release-hero-play / .release-hero-share in ReleaseHeroOverlay.razor —
|
||||
see deepdrft-styles.css. No co-located colour rule here: a wrapper-level override could not
|
||||
reach the .mud-secondary-text !important glyph anyway. */
|
||||
|
||||
@media (max-width: 599.98px) {
|
||||
.release-hero {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
the shared WaveformVisualizerControlState and raises Changed; the visualizer bridge subscribes. This
|
||||
host only toggles open/closed and centers the panel — it stays purely presentational. *@
|
||||
|
||||
<div class="dd-lava-lamp-trigger">
|
||||
<div class="dd-accent-icon">
|
||||
<MudTooltip Text="Visualizer settings">
|
||||
<MudIconButton Icon="@(_open ? DDIcons.LavaLampFilled : DDIcons.LavaLamp)"
|
||||
Size="@IconSize"
|
||||
|
||||
@@ -83,7 +83,7 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="cut-detail-actions">
|
||||
<div class="cut-detail-actions dd-accent-icon dd-accent-fill">
|
||||
@* Header Play loads the full album into the queue at index 0 (§3.4 seam,
|
||||
closed P11 W1). Disabled until at least one streamable track is resolved. *@
|
||||
<MudButton Variant="Variant.Filled"
|
||||
@@ -133,7 +133,7 @@ else
|
||||
{
|
||||
var track = ViewModel.Tracks[i];
|
||||
var index = i;
|
||||
<div class="cut-detail-track-row">
|
||||
<div class="cut-detail-track-row dd-accent-icon">
|
||||
<span class="cut-detail-track-number">@track.TrackNumber</span>
|
||||
<div class="cut-detail-track-play">
|
||||
<PlayStateIcon Track="@track"
|
||||
|
||||
@@ -751,68 +751,70 @@ body:has(.waveform-visualizer-control-overlay) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark-theme interactive affordances in the Cut detail header and track rows.
|
||||
In dark mode, Color.Secondary resolves to off-white (#FAFAF8), making filled
|
||||
and icon buttons unreadable on the light-ish dark-navy surface. Override to
|
||||
green (--deepdrft-primary = green-accent in dark, --deepdrft-navy as text) to
|
||||
match the "green = interactive" convention (see hero button dark rule above).
|
||||
Scoped to .cut-detail-actions and .cut-detail-track-row so the hero-overlay
|
||||
icons (.release-hero-play / .release-hero-share, forced white in
|
||||
ReleaseHeroOverlay.razor.css) are never touched. */
|
||||
/* =============================================================================
|
||||
INTERACTIVE-ACCENT ICON TREATMENT (.dd-accent-icon / .dd-accent-fill)
|
||||
----------------------------------------------------------------------------
|
||||
The single, reusable green-accent treatment for interactive icon affordances —
|
||||
replaces the per-site dark-mode overrides that previously had to fight the palette.
|
||||
|
||||
/* Play button (Variant.Filled, Color.Secondary) in the cut detail header */
|
||||
.deepdrft-theme-dark .cut-detail-actions .mud-button-filled {
|
||||
background-color: var(--deepdrft-primary);
|
||||
color: var(--gradient-base);
|
||||
WHY a class and not a palette colour: no MudBlazor Color enum is green in BOTH
|
||||
themes (Dark.Secondary is off-white, Dark.Primary is green; Light.Secondary is
|
||||
green, Light.Primary is navy), so every "green in both" affordance had to be
|
||||
patched per-site. --deepdrft-green-accent (#3D7A68) is the brand constant — the
|
||||
SAME value in both palettes — so a non-theme-scoped rule is correct: light already
|
||||
renders these glyphs green-accent (via Color.Secondary → Light.Secondary), so this
|
||||
keeps light pixel-identical while fixing dark.
|
||||
|
||||
WHY it reaches the glyph: MudBlazor colours a Color.Secondary icon by stamping
|
||||
.mud-secondary-text on the inner .mud-icon-root <svg>, and that rule is `!important`
|
||||
(color: var(--mud-palette-secondary) !important). Targeting only the .mud-icon-button
|
||||
wrapper therefore never wins — the svg keeps its own !important colour. The documented
|
||||
override bug. To beat an !important declaration we need our own !important AND equal-or-
|
||||
higher specificity: .dd-accent-icon .mud-icon-root (0,2,0) ties .mud-icon-root.mud-
|
||||
secondary-text (0,2,0) and wins on source order (deepdrft-styles.css is linked LAST in
|
||||
App.razor, after MudBlazor + isolated CSS). The .mud-icon-button selector carries the
|
||||
Color.Inherit affordances (lava-lamp glyph inherits the wrapper colour, no
|
||||
.mud-secondary-text to fight); the spinner covers the PlayStateIcon loading state.
|
||||
|
||||
Apply .dd-accent-icon to a CONTAINER of the affordance(s); add .dd-accent-fill
|
||||
alongside it when the container ALSO holds a filled MudButton whose Color.Secondary
|
||||
fill must go green-accent in dark (a filled button is a background fill, not a glyph —
|
||||
light already renders green-accent fill + white text, so .dd-accent-fill is DARK-ONLY
|
||||
to keep light pixel-identical). The Session/Mix hero Share/Play glyphs use this class
|
||||
too (they were already green-accent in light via Color.Secondary, so folding them in
|
||||
keeps light pixel-identical and fixes dark — the over-image glyphs are not actually
|
||||
theme-divergent). The one genuinely theme-divergent affordance (gas-lamp = inherited
|
||||
nav text in light) does NOT use this class — it keeps a dark-only rule below.
|
||||
|
||||
The glyph rule targets glyphs inside an ICON button (.mud-icon-button .mud-icon-root)
|
||||
only — the filled Play button is a .mud-button-filled (not .mud-icon-button), so its
|
||||
StartIcon is naturally excluded and keeps its own contrast colour (white in light,
|
||||
navy in dark). The bare .mud-icon-button selector carries the Color.Inherit case
|
||||
(lava-lamp glyph inherits the wrapper colour); the spinner covers the loading state. */
|
||||
.dd-accent-icon .mud-icon-button .mud-icon-root,
|
||||
.dd-accent-icon .mud-icon-button,
|
||||
.dd-accent-icon .mud-progress-circular {
|
||||
color: var(--deepdrft-green-accent) !important;
|
||||
}
|
||||
|
||||
/* Share + Queue icon buttons in the cut detail header */
|
||||
.deepdrft-theme-dark .cut-detail-actions .mud-icon-button {
|
||||
color: var(--deepdrft-primary);
|
||||
/* Filled-button variant (DARK-ONLY): green-accent fill + navy glyph/label, matching the
|
||||
play-chip language. In dark, Color.Secondary fill resolves to off-white (unreadable);
|
||||
here it becomes a clear green CTA. Light is untouched (already green fill + white text). */
|
||||
.deepdrft-theme-dark .dd-accent-fill .mud-button-filled {
|
||||
background-color: var(--deepdrft-green-accent);
|
||||
color: var(--deepdrft-navy);
|
||||
}
|
||||
|
||||
/* Share + Queue icon buttons in each cut detail track row */
|
||||
.deepdrft-theme-dark .cut-detail-track-row .mud-icon-button {
|
||||
color: var(--deepdrft-primary);
|
||||
.deepdrft-theme-dark .dd-accent-fill .mud-button-filled .mud-icon-root {
|
||||
color: var(--deepdrft-navy) !important;
|
||||
}
|
||||
|
||||
/* Dark-theme interactive affordances in the Session/Mix release-detail hero overlay.
|
||||
The co-located ReleaseHeroOverlay.razor.css forces .release-hero-play and
|
||||
.release-hero-share icons to --deepdrft-white unconditionally (correct for the
|
||||
over-image light-theme contract). In dark theme the play and share glyphs adopt
|
||||
green to match the "green = interactive" convention. Both the scoped ::deep rule
|
||||
([b-xxx] .release-hero-play .mud-icon-button) and this global rule
|
||||
(.deepdrft-theme-dark .release-hero-play .mud-icon-button) compute to specificity
|
||||
(0,3,0) — a tie — so the override wins on source order: deepdrft-styles.css is
|
||||
linked after DeepDrftPublic.styles.css in App.razor, making it the later rule.
|
||||
No !important needed. .mud-progress-circular is included alongside .mud-icon-button
|
||||
in the play slot to colour the loading spinner green as well.
|
||||
Light theme: pixel-identical to before. */
|
||||
.deepdrft-theme-dark .release-hero-play .mud-icon-button,
|
||||
.deepdrft-theme-dark .release-hero-play .mud-progress-circular,
|
||||
.deepdrft-theme-dark .release-hero-share .mud-icon-button {
|
||||
color: var(--deepdrft-primary);
|
||||
}
|
||||
|
||||
/* Dark-theme lava-lamp visualizer-settings trigger.
|
||||
The MudIconButton uses Color.Secondary, which resolves to off-white in dark mode.
|
||||
The .dd-lava-lamp-trigger marker div (added to WaveformVisualizerControlPopover.razor)
|
||||
scopes this rule to only that trigger, preventing bleed onto other Secondary icon
|
||||
buttons sharing the same host pages. Applied at all host sites (Mix, Cut, Session,
|
||||
NowPlaying) since the wrapper travels with the component. Light theme: unchanged. */
|
||||
.deepdrft-theme-dark .dd-lava-lamp-trigger .mud-icon-button {
|
||||
color: var(--deepdrft-primary);
|
||||
}
|
||||
|
||||
/* Dark-theme gas-lamp dark-mode toggle.
|
||||
The MudIconButton uses Color.Inherit and sits inside .dd-nav-actions (the right-side
|
||||
cluster of the nav bar). In dark theme it inherits the nav text colour (off-white);
|
||||
green makes it consistent with the "green = interactive" convention. The selector is
|
||||
scoped to .dd-nav-actions so no other icon buttons are affected. Covers both the
|
||||
desktop nav and the mobile nav (which also renders its gas-lamp inside .dd-nav-actions).
|
||||
Light theme: unchanged. */
|
||||
/* Theme-divergent affordance — gas-lamp dark-mode toggle.
|
||||
Uses Color.Inherit, so in LIGHT it inherits the nav text colour (the contract to keep).
|
||||
In dark theme it goes green-accent to match the convention. Scoped to .dd-nav-actions
|
||||
(covers both desktop and mobile nav, which both render the gas-lamp there); dark-only. */
|
||||
.deepdrft-theme-dark .dd-nav-actions .mud-icon-button {
|
||||
color: var(--deepdrft-primary);
|
||||
color: var(--deepdrft-green-accent);
|
||||
}
|
||||
|
||||
/* =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user