389 lines
29 KiB
Markdown
389 lines
29 KiB
Markdown
# Track View CSS Consolidation — audit + consolidated architecture spec
|
||
|
||
Status: completed. Author: product-designer. Date: 2026-06-05. Implementer: maintenance-engineer. Landed: 2026-06-05.
|
||
|
||
Predecessors (both landed 2026-06-05):
|
||
- [`track-card-theming.md`](track-card-theming.md) — landed the navy-glass fallback + moss-green
|
||
text treatment, scoped under `.deepdrft-theme-dark` / `-light`.
|
||
- [`track-card-css-architecture.md`](track-card-css-architecture.md) — replaced the MudCard/MudPaper
|
||
shell with plain `<div>`s and removed the four `!important` declarations from section 8.
|
||
|
||
This note is the consolidation pass *after* those two landed. The cards render correctly today; the
|
||
goal here is a single coherent description of the cascade as it now stands, identification of what is
|
||
redundant or fragile, and a prioritized set of changes. **Nothing here is urgent or broken** — it is
|
||
hygiene plus two genuine hierarchy issues (§5) that the predecessor notes flagged as optional and that
|
||
are now worth re-deciding because the design converged on green-everywhere.
|
||
|
||
The headline finding: **the architecture is already close to clean.** The plain-div shell removed the
|
||
hard MudBlazor fight. What remains is (a) one real cascade subtlety on `MudText` color, (b) an open-but-
|
||
unused CSS-isolation seam, (c) layout-container redundancy across three files, and (d) a green-on-green
|
||
hierarchy collapse now that artist text, the genre chip, and the play FAB are all the same accent green.
|
||
|
||
---
|
||
|
||
## §1. Inventory — every CSS declaration touching the track view
|
||
|
||
Render tree (confirmed): `MainLayout` → `<div class="@ThemeWrapperClass">` (the
|
||
`deepdrft-theme-dark` / `-light` ancestor, wraps `@Body`) → `TracksView` (`.tracks-page-wrapper` …)
|
||
→ `TracksGallery` (`MudContainer.tracks-gallery-container` → `MudGrid` → `MudItem` →
|
||
`.deepdrft-track-gallery-item-center`) → `TrackCard` (plain-div shell).
|
||
|
||
Stylesheet load order (from `App.razor`, per the header comment in `deepdrft-styles.css`):
|
||
`deepdrft-tokens.css` (tokens) → `deepdrft-styles.css` (rules) → MudBlazor framework CSS + the
|
||
`MudThemeProvider`-injected `:root` palette variables. The `--mud-palette-*` vars are runtime-injected
|
||
and update on `IsDarkMode` toggle.
|
||
|
||
| # | Rule / selector | File | Targets | Conflict status |
|
||
|---|---|---|---|---|
|
||
| 1 | `.tracks-page-wrapper` (flex column) | `TracksView.razor.css` (scoped) | page outer wrapper | none — but **functionally inert**: `flex-direction: column` with no height constraint and a single flex child does nothing visible. See §4. |
|
||
| 2 | `.tracks-view-container` (flex column, `flex:1`, `padding:0 16px`) | `TracksView.razor.css` (scoped) | inner container | Horizontal padding here **stacks** with #5 and #6 (16px + 16px + 16px = 48px effective inset before the grid). See §4. |
|
||
| 3 | `.tracks-content` (flex, `flex-grow:1`, `padding-top:16px`) | `TracksView.razor.css` (scoped) | content row | `display:flex` on a block that contains a single full-width `MudContainer` — flex adds nothing here. Mild redundancy. |
|
||
| 4 | `.tracks-footer` (flex column, center, gap) | `TracksView.razor.css` (scoped) | pagination footer | none. Clean. |
|
||
| 5 | `.tracks-gallery-container` (`padding:16px`, `height:100%`, `box-sizing`) | `TracksGallery.razor.css` (scoped) | `MudContainer` root | `padding:16px` **competes with** #2's horizontal padding (double inset). `height:100%` is inert (no constrained-height ancestor chain). See §4. |
|
||
| 6 | `MudContainer MaxWidth="Large"` (MudBlazor) | MudBlazor framework | gallery container | MudBlazor's own `.mud-container` adds responsive horizontal padding + max-width. Stacks on top of #2 and #5 — **three** sources of horizontal inset. |
|
||
| 7 | `.deepdrft-track-gallery-item-center` (`display:flex; justify-content:center`) | `deepdrft-styles.css` §8 (global) | per-item wrapper div | none functionally, but **lives in global §8 while its only consumers are two scoped components** — see §3/§4 placement note. Appears twice (TracksView skeleton path + TracksGallery). |
|
||
| 8 | `.deepdrft-track-card-container` (250×250, relative, overflow hidden, `background:transparent`) | `deepdrft-styles.css` §8 (global) | card shell div | none — `!important` already removed; plain div wins by default. |
|
||
| 9 | `.deepdrft-theme-dark .deepdrft-track-card-container` (glass border) | §8 (global) | card shell, dark | none. **Ancestor-dependent** (needs `.deepdrft-theme-dark`) — cannot move to scoped CSS. |
|
||
| 10 | `.deepdrft-track-card-bg` (absolute full-bleed, `brightness(0.7)`) | §8 (global) | album-art div | none. Theme-agnostic — **could** move to scoped. |
|
||
| 11 | `.deepdrft-track-card-content` (relative, flex column, space-between, padding) | §8 (global) | content div | none. Theme-agnostic layout — **could** move to scoped. |
|
||
| 12 | `.deepdrft-theme-dark .deepdrft-track-card-content` (navy scrim gradient) | §8 (global) | content div, dark | none. **Ancestor-dependent** — stays global. |
|
||
| 13 | `.deepdrft-track-card-fallback` (absolute full-bleed, `background:navy-mid`) | §8 (global) | fallback div | none. Base paint is the no-wrapper-class flash guard. |
|
||
| 14 | `.deepdrft-theme-dark .deepdrft-track-card-fallback` (navy-glass, border, blur) | §8 (global) | fallback, dark | none. **Ancestor-dependent** — stays global. |
|
||
| 15 | `.deepdrft-theme-light .deepdrft-track-card-fallback` (navy-tint-on-white, border) | §8 (global) | fallback, light | none. **Ancestor-dependent** — stays global. |
|
||
| 16 | `.deepdrft-track-title { color: white }` (unconditional) | §8 (global) | title MudText | **competes with MudBlazor `.mud-typography-subtitle1` color.** See §2. |
|
||
| 17 | `.deepdrft-track-artist { color: green-accent }` (unconditional) | §8 (global) | artist MudText | **competes with MudBlazor `.mud-typography-caption` color.** See §2. |
|
||
| 18 | `.deepdrft-track-meta { color: rgba(white,.55) }` (unconditional) | §8 (global) | album/year MudText | **competes with MudBlazor `.mud-typography-caption` color.** See §2. |
|
||
| 19 | `.deepdrft-theme-light .deepdrft-track-title / -artist / -meta` (navy/green/muted) | §8 (global) | text, light | Light overrides of #16–18. Correct intent; cascade verified in §2. |
|
||
| 20 | `.deepdrft-track-info-middle { margin: 8px 0 }` | §8 (global) | middle row | none. Theme-agnostic — could move to scoped. |
|
||
| 21 | `.deepdrft-track-info-bottom` (flex, space-between, center) | §8 (global) | bottom row | none. Theme-agnostic — could move to scoped. (Note: `.deepdrft-track-info-top` has **no** rule — relies on default block flow; fine.) |
|
||
| 22 | `.deepdrft-genre-chip { opacity:.9; margin-top:4px }` | §8 §9 (global) | genre MudChip | No color override — chip inherits `Color.Primary` = green in dark. See §5. |
|
||
| 23 | `MudChip Color="Color.Primary"` (MudBlazor) | MudBlazor framework | genre chip | Renders filled `--mud-palette-primary` = `#3D7A68` green (dark). **Collides with green artist text + green FAB.** See §5. |
|
||
| 24 | `MudFab Color="Color.Primary"` (MudBlazor) | MudBlazor framework | play button | Renders green (dark). Intended as primary interactive accent, but now one of three greens in the card. See §5. |
|
||
| 25 | `@media (max-width:480px) .deepdrft-track-card-container` (200×200) | §8 §13 (global) | card shell, small screens | none. Theme-agnostic — would move to scoped *with* the container rule if §3 adopted. |
|
||
| — | `MudText Typo` font-family (subtitle1 → Geist Mono, caption → Geist Mono) | `DeepDrftPalettes.Typography` (MudBlazor theme) | all card text | Not a conflict — font comes from the theme typography object, color comes from our §8 rules. The two concerns are cleanly separated **once §2 confirms color wins.** |
|
||
|
||
---
|
||
|
||
## §2. MudText cascade analysis — per element, both themes
|
||
|
||
This is the part Daniel flagged. The question: do our single-class `.deepdrft-track-*` color rules
|
||
reliably beat MudBlazor's `.mud-typography-*` color rules, and do they behave correctly in **both**
|
||
themes given the unconditional-default + light-override structure?
|
||
|
||
### The two competing selectors
|
||
|
||
For each card text element MudBlazor renders `<p class="mud-typography mud-typography-{typo} deepdrft-track-{role} ...">`. Two color sources apply:
|
||
|
||
- **MudBlazor:** `.mud-typography-subtitle1` / `.mud-typography-caption` — these set font properties.
|
||
The **color** for body/caption/subtitle typography comes from `.mud-typography` (or the element
|
||
default) resolving to `color: var(--mud-palette-text-primary)`. Specificity: single class, `0,1,0`.
|
||
(MudBlazor does not put a per-`Typo` color on `.mud-typography-caption`; caption/subtitle inherit the
|
||
text-primary color. The relevant competing declaration is therefore `0,1,0` — a single class.)
|
||
- **Ours, dark (unconditional default):** `.deepdrft-track-title { color: white }` — `0,1,0`.
|
||
- **Ours, light (theme override):** `.deepdrft-theme-light .deepdrft-track-title { color: navy }` — `0,2,0`.
|
||
|
||
### Why ours wins in dark — the load-order fact
|
||
|
||
In **dark mode**, ours (`0,1,0`) ties MudBlazor's (`0,1,0`) on specificity, so **source order decides.**
|
||
`deepdrft-styles.css` is authored to load, and the MudBlazor framework sheet loads as a separate link.
|
||
The decisive detail: the *color* MudBlazor would otherwise apply is `var(--mud-palette-text-primary)`,
|
||
and our rule sets an explicit color literal. **As long as `deepdrft-styles.css` is linked after the
|
||
MudBlazor framework CSS, ours wins the tie.** Per `App.razor` ordering (tokens → our styles, with the
|
||
MudThemeProvider injecting palette vars), this holds today — but it is a **source-order dependency, not
|
||
a specificity guarantee.** That is the one fragility in the dark path: it works because of link order,
|
||
the same class of latent risk the predecessor note removed for backgrounds. It is lower-risk here
|
||
(both are `0,1,0`, and the visible result is correct), but it is the same smell.
|
||
|
||
### Per-element cascade table
|
||
|
||
| Element | Typo | Dark winner | Dark correct? | Light winner | Light correct? |
|
||
|---|---|---|---|---|---|
|
||
| Title (`-track-title`) | subtitle1 | `.deepdrft-track-title` (white, `0,1,0`, wins tie by load order) | ✅ off-white on navy-glass | `.deepdrft-theme-light .deepdrft-track-title` (navy, `0,2,0`) | ✅ navy on light fallback |
|
||
| Artist (`-track-artist`) | caption | `.deepdrft-track-artist` (green-accent, `0,1,0`) | ✅ legible, but see §5 (green collision) | `.deepdrft-theme-light .deepdrft-track-artist` (deep green, `0,2,0`) | ✅ deep green on light |
|
||
| Album (`-track-meta`) | caption | `.deepdrft-track-meta` (rgba white .55, `0,1,0`) | ✅ muted off-white | `.deepdrft-theme-light .deepdrft-track-meta` (muted, `0,2,0`) | ✅ muted on light |
|
||
| Year (`-track-meta`) | caption | same as album | ✅ | same as album | ✅ |
|
||
|
||
### Does the "unconditional default + light override" structure behave correctly?
|
||
|
||
**Yes, in both themes — with one caveat.** The structure is:
|
||
|
||
- **Dark:** unconditional defaults apply (no `.deepdrft-theme-dark` guard). They win the `0,1,0` tie
|
||
against MudBlazor by load order. ✅
|
||
- **Light:** the `.deepdrft-theme-light` overrides (`0,2,0`) beat the unconditional defaults (`0,1,0`).
|
||
✅ — this is why the unconditional dark defaults do **not** leak into light mode. The light overrides
|
||
correctly win on specificity, not load order, so light is *more* robust than dark.
|
||
|
||
**Caveat — the no-wrapper hydration window.** The unconditional defaults exist specifically to cover the
|
||
brief WASM-hydration window where neither `.deepdrft-theme-dark` nor `.deepdrft-theme-light` is on the
|
||
ancestor yet (the predecessor "blue text flash" fix). During that window the card shows dark-default
|
||
colors (white title, green artist) regardless of the user's actual theme. For a **light-mode** user this
|
||
means a sub-second flash of dark-intended text before the `-light` override attaches. It is the
|
||
symmetric cost of choosing dark as the unconditional default. Acceptable (the flash is brief and the
|
||
fallback panel underneath is also painted by a base navy-mid rule, so the card is internally
|
||
consistent during the flash), but worth naming: **the flash guard optimizes for the dark-mode user at
|
||
the light-mode user's slight expense.**
|
||
|
||
### Better alternative to the unconditional-default approach
|
||
|
||
There is a cleaner structure that removes both the load-order tie *and* the asymmetric flash, at the
|
||
cost of one extra rule per element. See §6 R2 — promote the dark treatment to a `.deepdrft-theme-dark`
|
||
guard and keep a theme-neutral safe default. Recommended only if the load-order dependency is judged
|
||
worth hardening; the current code is visually correct.
|
||
|
||
---
|
||
|
||
## §3. Recommended scoped/global split for TrackCard
|
||
|
||
The predecessor architecture note established the key fact: with a plain-div shell, **Blazor CSS
|
||
isolation now works** for elements Blazor renders (the shell divs), but **cannot select ancestors** —
|
||
so any rule keyed on `.deepdrft-theme-dark` / `-light` must stay global. The seam is open but unused.
|
||
|
||
The clean seam: **scoped CSS owns theme-agnostic structure/layout; global §8 owns everything that
|
||
depends on the theme-wrapper ancestor (all paint/color that differs by theme).**
|
||
|
||
### What CAN move to a new `TrackCard.razor.css` (theme-agnostic, Blazor-rendered divs)
|
||
|
||
| Rule | Why it can move |
|
||
|---|---|
|
||
| `.deepdrft-track-card-container` (size, position, overflow, `background:transparent`) | No theme dependency. Note: the `@media 480px` override (#25) must move **with** it. |
|
||
| `.deepdrft-track-card-bg` (album-art positioning + brightness) | Pure layout/filter, theme-agnostic. |
|
||
| `.deepdrft-track-card-content` (base flex/padding/z-index — **not** the dark scrim) | Base layout is theme-agnostic. |
|
||
| `.deepdrft-track-info-middle` / `-info-bottom` | Pure layout. |
|
||
|
||
### What MUST stay global in §8 (ancestor-dependent — scoped CSS can't reach `.deepdrft-theme-*`)
|
||
|
||
| Rule | Why it stays |
|
||
|---|---|
|
||
| `.deepdrft-theme-dark .deepdrft-track-card-container` (glass border) | Selects the theme ancestor. |
|
||
| `.deepdrft-theme-dark .deepdrft-track-card-content` (scrim gradient) | Selects the theme ancestor. |
|
||
| `.deepdrft-track-card-fallback` + both theme variants | Theme ancestor (and the base flash-guard rule pairs with them — keep the set together). |
|
||
| All `.deepdrft-track-title / -artist / -meta` color rules (both default and `-light`) | Theme-dependent color; the `-light` variants select the ancestor. Keeping the unconditional defaults global alongside them keeps the whole color story in one place. |
|
||
| `.deepdrft-genre-chip` color treatment (if added per §5) | If theme-aware, ancestor-dependent. |
|
||
|
||
### Is there a clean seam? — Recommendation
|
||
|
||
**Yes, but it is a judgment call whether to take it.** Two coherent end-states:
|
||
|
||
- **Option S-1 (split): introduce `TrackCard.razor.css`** holding the four theme-agnostic structural
|
||
rules above; leave all theme/color rules in §8. *Pro:* co-locates structure with the component,
|
||
uses the seam the predecessor note reopened, shrinks §8. *Con:* splits the card's CSS across two
|
||
files — a maintainer now looks in two places, and the split line ("is this theme-dependent?") is a
|
||
subtlety that invites future mis-filing. It also lives in `DeepDrftShared.Client` (the component's
|
||
home), which means the CMS would now ship these structural rules too (harmless — they are layout
|
||
only — but a new coupling).
|
||
- **Option S-2 (consolidate, recommended): keep everything in §8, do not create a scoped file.**
|
||
The card's CSS is small (~15 rules) and *already* all in one place. The strongest property of the
|
||
current setup is that **all track-card CSS lives in exactly one section of one file.** Splitting it
|
||
to use isolation "because we can" trades that single-location clarity for architectural tidiness
|
||
that buys little — the theme-dependent majority must stay global regardless, so a split leaves the
|
||
bulk in §8 and scatters only four layout rules. **Recommend S-2.** Keep the seam *open and
|
||
documented* (it is a real option for future per-state styling — hover/selected/now-playing — which
|
||
may be theme-agnostic and numerous enough to justify a scoped file later), but do not split now.
|
||
|
||
This matches the predecessor note's own framing: §8's public-only scoping is "still convenient"; the
|
||
isolation seam is a reopened *option*, not a mandate.
|
||
|
||
---
|
||
|
||
## §4. TracksView / TracksGallery layout assessment — competing
|
||
|
||
This is the one place with genuine redundancy and mild competition. Three layers each contribute
|
||
horizontal inset and flex behavior that doesn't compose cleanly:
|
||
|
||
### The triple horizontal inset
|
||
|
||
A track card's left edge is pushed in by **three stacked paddings**:
|
||
1. `.tracks-view-container { padding: 0 16px }` (TracksView scoped)
|
||
2. `MudContainer`'s own responsive padding + `MaxWidth.Large` cap (MudBlazor)
|
||
3. `.tracks-gallery-container { padding: 16px }` (TracksGallery scoped)
|
||
|
||
Three sources of "indent the gallery" with no single owner. Not *broken* (it renders; the grid just
|
||
sits inside a compounded margin), but it is exactly the "where does this spacing come from?" confusion
|
||
that consolidation should remove. **One layer should own horizontal inset.** Since `MudContainer`
|
||
already provides centered, capped, responsive padding (its entire purpose), the two hand-rolled 16px
|
||
paddings are redundant with it.
|
||
|
||
### Inert / no-op rules
|
||
|
||
- `.tracks-page-wrapper` `flex-direction: column` — single child, no height target. Does nothing.
|
||
- `.tracks-view-container` `flex: 1` and `.tracks-content` `flex-grow: 1` — these presume a constrained-
|
||
height flex parent so the content stretches and the footer pins to the bottom. But `.tracks-page-
|
||
wrapper` has **no height** (no `min-height: 100vh`, no `flex: 1` against a sized parent). The flex-grow
|
||
chain has nothing to grow into, so the "sticky footer" intent these rules encode is **not actually
|
||
achieved** — the footer sits directly under the content regardless. Dead intent.
|
||
- `.tracks-gallery-container { height: 100% }` — no constrained-height ancestor, so `100%` resolves to
|
||
content height. Inert.
|
||
- `.tracks-content { display: flex }` — wraps a single full-width `MudContainer`; flex changes nothing.
|
||
|
||
### Centering
|
||
|
||
`.deepdrft-track-gallery-item-center` (global §8) + `MudGrid Justify="Justify.Center"` (TracksGallery)
|
||
+ `MudItem` breakpoints. These **do** cooperate correctly — the per-item flex-center plus grid justify
|
||
center the cards within their columns and center the row. No conflict here; this part works.
|
||
|
||
### Recommendation (detail in §6 R3)
|
||
|
||
Collapse to a single ownership model: let `MudContainer` own horizontal inset and max-width; drop the
|
||
redundant paddings; either *commit* to the sticky-footer intent (give the wrapper a real height target
|
||
so the flex chain works) or *remove* the inert flex rules. The skeleton-loading path in `TracksView`
|
||
(which uses its own `MudGrid` + `.deepdrft-track-gallery-item-center`, bypassing `TracksGallery`)
|
||
should match whatever container model the loaded path uses, so the layout doesn't shift on load.
|
||
|
||
---
|
||
|
||
## §5. Genre chip and FAB hierarchy — re-assessment
|
||
|
||
The predecessor note flagged the genre chip as §3a (optional) and explicitly blessed the FAB as
|
||
correct (§3b). **That assessment was made before the design converged on green-for-everything.** Re-
|
||
evaluating now that artist text is *also* green-accent:
|
||
|
||
### The collapse
|
||
|
||
In **dark mode**, three elements in the lower half of every card are the **same** `#3D7A68` green-
|
||
accent:
|
||
1. **Artist text** (`.deepdrft-track-artist` → green-accent)
|
||
2. **Genre chip** (`MudChip Color="Color.Primary"` → filled green)
|
||
3. **Play FAB** (`MudFab Color="Color.Primary"` → green)
|
||
|
||
Three greens stacked in the card's lower two rows flattens the visual hierarchy: the *interactive*
|
||
element (the FAB — the only thing you click) no longer stands out from the *informational* green
|
||
(artist) and the *categorical* green (genre tag). The eye can't tell what's actionable. This is a
|
||
real regression introduced by the convergence on green, not a pre-existing nit.
|
||
|
||
### Resolving it — assign each green a distinct job
|
||
|
||
The fix is to **let only one of the three keep the saturated green**, and give it to the interactive
|
||
element (the FAB), since green-accent is defined as the dark palette's *primary interactive* color.
|
||
Demote the other two:
|
||
|
||
- **FAB → keep `Color.Primary` (green).** It is the action; it earns the accent. ✅ Confirmed correct
|
||
*only if* the other two greens step back. The predecessor's "FAB is fine" verdict holds **conditional
|
||
on fixing the chip and artist**.
|
||
- **Genre chip → stop being a filled green.** Make it a *tag*, not a button-lookalike. Recommend an
|
||
outlined / low-emphasis treatment: transparent (or navy-mid) ground, green-accent border + text, so
|
||
it reads as a category label distinct from the solid green FAB. This is the predecessor §3a proposal,
|
||
now upgraded from "optional" to **recommended** because the collision is no longer hypothetical.
|
||
- **Artist text → consider demoting from green to muted off-white.** This is the genuinely open
|
||
design call. Two readings (the predecessor's §2b ambiguity resurfacing): (a) **artist stays green**
|
||
as the card's identity color and we rely on the chip-outline + FAB-fill contrast to separate the
|
||
three; or (b) **artist goes muted off-white** (like `.np-sub` in NowPlayingCard, `rgba(250,250,248,.45–.55)`),
|
||
leaving green as a *purely interactive/accent* signal (FAB + chip border). **Recommend (b).** It
|
||
matches NowPlayingCard's actual hierarchy most closely — there, green is the *label/accent* color
|
||
(`.np-label`, waveform), the title is off-white, and the sub is muted; artist-as-sub should be muted,
|
||
not accent. Reading (b) also reserves green for "this means something / do something," which is the
|
||
cleaner semantic. This reverses the predecessor's "reading 1" default — justified because that
|
||
default was chosen before the three-green collision was visible.
|
||
|
||
### Light mode
|
||
|
||
Less acute: in light, `Color.Primary` = navy `#0D1B2A`, artist = deep green `#1A3C34`, so the three
|
||
elements are navy chip / navy FAB / green artist — already two distinct hues. The chip-vs-FAB
|
||
sameness (both navy) is the only light-mode echo of the problem; the outlined-chip treatment fixes it
|
||
there too. No separate light fix needed beyond the theme-aware chip rule.
|
||
|
||
---
|
||
|
||
## §6. Recommended changes — prioritized, ready for maintenance-engineer
|
||
|
||
Ordered by value. R1 and R4 are pure cleanup (safe, low-risk). R5 is the real design change. R2 and
|
||
R3 are judgment calls flagged for Daniel.
|
||
|
||
### R1 — Resolve the triple horizontal inset (cleanup, recommended)
|
||
**Priority: high (clarity). Risk: low (visual nudge only).**
|
||
Pick `MudContainer` as the single owner of horizontal inset + max-width. In
|
||
`TracksView.razor.css`: change `.tracks-view-container { padding: 0 16px }` → `padding: 0`. In
|
||
`TracksGallery.razor.css`: remove `padding: 16px` from `.tracks-gallery-container` (keep `box-sizing`;
|
||
drop `height: 100%` as inert — see R3). The cards will sit at MudContainer's natural inset. Verify the
|
||
gallery still has breathing room at the page edge; if MudContainer's default padding feels too tight,
|
||
add a *single* deliberate gutter on `.tracks-gallery-container` and document it as the one owner.
|
||
|
||
### R2 — Harden the MudText color cascade (judgment call, optional)
|
||
**Priority: medium. Risk: low. Decision needed from Daniel.**
|
||
Today the dark text colors win MudBlazor by **load order** (a `0,1,0` tie). It renders correctly but is
|
||
the same latent fragility the predecessor note removed for backgrounds. Two ways to harden, if desired:
|
||
- **R2a (minimal):** leave as-is, add a comment in §8 noting the load-order dependency and that
|
||
`deepdrft-styles.css` must stay linked after the MudBlazor framework sheet. Zero visual change.
|
||
- **R2b (structural):** restructure to remove the tie *and* the asymmetric light-flash:
|
||
give each text element a theme-neutral safe default (e.g. `inherit` / muted) at `0,1,0`, then guard
|
||
**both** dark and light treatments under their respective `.deepdrft-theme-*` ancestors (`0,2,0`).
|
||
This makes both themes win on **specificity, not load order**, and removes the dark-defaults-leak
|
||
during a light user's hydration window (the flash becomes a neutral-to-light transition, not dark-to-
|
||
light). Cost: one extra rule per element (3 default + 3 dark + 3 light instead of 3 default + 3 light).
|
||
**Recommend R2b only if Daniel wants the cascade hardened; otherwise R2a.** The current code is not
|
||
wrong — this is hardening, not a bug fix.
|
||
|
||
### R3 — Decide the sticky-footer intent (judgment call)
|
||
**Priority: medium. Risk: low.**
|
||
The `flex: 1` / `flex-grow: 1` / `height: 100%` chain in `TracksView.razor.css` +
|
||
`TracksGallery.razor.css` encodes a sticky-footer-at-viewport-bottom intent that **does not currently
|
||
work** (no height target on `.tracks-page-wrapper`). Resolve one way:
|
||
- **R3a (commit):** add `min-height: 100vh` (or `flex: 1` against a sized layout parent) to
|
||
`.tracks-page-wrapper` so the flex-grow chain actually pins the pagination footer to the bottom on
|
||
short pages. Then the existing flex rules become meaningful.
|
||
- **R3b (remove, recommended):** delete the inert flex/height rules (`.tracks-page-wrapper`
|
||
flex-direction; `.tracks-view-container` flex/flex:1; `.tracks-content` display:flex/flex-grow;
|
||
`.tracks-gallery-container` height:100%). The page already lays out fine via normal block flow + the
|
||
footer's own centering. **Recommend R3b** — the sticky-footer intent isn't visibly needed on a
|
||
paginated gallery (content fills the page), and removing dead rules is cleaner than reviving an
|
||
unused behavior. If Daniel *wants* the footer pinned on sparse pages, do R3a instead.
|
||
|
||
### R4 — Tidy redundant layout rules (cleanup, recommended)
|
||
**Priority: low. Risk: none.**
|
||
Independent of R3: `.tracks-content { display: flex }` wraps a single full-width child — drop
|
||
`display: flex` (keep `padding-top: 16px`). These are no-ops being removed, not behavior changes. Roll
|
||
into the R3 pass.
|
||
|
||
### R5 — Break the three-green collision (design change, recommended)
|
||
**Priority: high (visual quality). Risk: low-medium (visible design change — wants Daniel's eye).**
|
||
This is the substantive one. In dark mode, artist text + genre chip + play FAB are all the same green.
|
||
Reassign:
|
||
- **Genre chip:** add a theme-aware `.deepdrft-genre-chip` treatment — outlined / low-emphasis
|
||
(transparent or navy-mid ground, green-accent border + text in dark; navy border + text in light) so
|
||
it reads as a *tag*, not a filled button. Keep the existing `opacity:.9; margin-top:4px`. Because the
|
||
treatment is theme-aware it stays in global §8 (ancestor-dependent). Likely also requires changing the
|
||
Razor `MudChip` from `Variant.Filled Color.Primary` to `Variant.Outlined` (or `Variant.Text`) so
|
||
MudBlazor stops painting a filled green ground that our CSS then has to fight — *prefer changing the
|
||
Variant in Razor over CSS-overriding a filled chip*, consistent with the predecessor's "don't fight
|
||
MudBlazor, don't invite the fight" principle. **Razor edit owned by maintenance-engineer; flagged
|
||
here as part of the change.**
|
||
- **Artist text:** demote `.deepdrft-track-artist` from green-accent to muted off-white in dark
|
||
(`rgba(250,250,248,.55)`, matching `.deepdrft-track-meta` or slightly brighter for hierarchy), leaving
|
||
green as a purely accent/interactive signal. **This reverses the predecessor's "reading 1" default —
|
||
call it out to Daniel explicitly; it's a taste call he should confirm.** Light mode artist can stay
|
||
deep green (no collision there) or follow the same demotion for consistency.
|
||
- **FAB:** no change — keep `Color.Primary` green. It is now the *only* saturated green in the lower
|
||
card, so it correctly reads as the action.
|
||
|
||
**Net effect:** title off-white, artist muted, genre = outlined green tag, FAB = solid green action.
|
||
A clear three-tier hierarchy (identity / info / action) instead of a green wash. This is the
|
||
NowPlayingCard vocabulary applied correctly.
|
||
|
||
### R6 — Do NOT split TrackCard CSS into a scoped file (decision: hold)
|
||
**Priority: n/a (a decision to *not* act). Risk: n/a.**
|
||
Per §3, keep all track-card CSS in §8 (Option S-2). Document the open isolation seam in a §8 comment so
|
||
a future per-state styling pass (hover/selected/now-playing) knows a `TrackCard.razor.css` is available
|
||
for theme-agnostic state rules if they grow numerous. Do not create the file now.
|
||
|
||
### Things explicitly NOT to change
|
||
- The plain-div shell and absence of `!important` (predecessor work — correct, leave alone).
|
||
- The base flash-guard rules (`.deepdrft-track-card-fallback` base navy-mid; unconditional text
|
||
defaults) — these are load-bearing for the no-wrapper hydration window. R2b *restructures* them but
|
||
preserves the guard; do not simply delete them.
|
||
- `.deepdrft-track-gallery-item-center` location — it is used by both the loaded path (TracksGallery)
|
||
and the skeleton path (TracksView), so it correctly stays global. Leave in §8.
|
||
|
||
### Suggested sequencing
|
||
1. R1 + R4 + R3b together (one cleanup pass on the two scoped layout files — safe, no design risk).
|
||
2. R5 (the design change — wants Daniel's eye on the result; the artist-demotion is a taste call).
|
||
3. R2 only if Daniel elects to harden the cascade (R2a comment is near-free; R2b is the fuller fix).
|
||
|
||
---
|
||
|
||
## Open questions for Daniel
|
||
1. **R5 artist color:** demote artist from green to muted off-white (recommended), or keep artist green
|
||
and rely on chip-outline + FAB-fill to separate the three greens? This reverses a prior default.
|
||
2. **R3 sticky footer:** pin the pagination footer to viewport bottom on sparse pages (R3a), or remove
|
||
the dead flex rules and let it flow (R3b, recommended)?
|
||
3. **R2 cascade hardening:** leave the dark text colors winning by load order with a comment (R2a), or
|
||
restructure to win by specificity and remove the light-user hydration flash (R2b)?
|