# 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)?
