29 KiB
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— landed the navy-glass fallback + moss-green text treatment, scoped under.deepdrft-theme-dark/-light.track-card-css-architecture.md— replaced the MudCard/MudPaper shell with plain<div>s and removed the four!importantdeclarations 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 tocolor: var(--mud-palette-text-primary). Specificity: single class,0,1,0. (MudBlazor does not put a per-Typocolor on.mud-typography-caption; caption/subtitle inherit the text-primary color. The relevant competing declaration is therefore0,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-darkguard). They win the0,1,0tie against MudBlazor by load order. ✅ - Light: the
.deepdrft-theme-lightoverrides (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.cssholding 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 inDeepDrftShared.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:
.tracks-view-container { padding: 0 16px }(TracksView scoped)MudContainer's own responsive padding +MaxWidth.Largecap (MudBlazor).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-wrapperflex-direction: column— single child, no height target. Does nothing..tracks-view-containerflex: 1and.tracks-contentflex-grow: 1— these presume a constrained- height flex parent so the content stretches and the footer pins to the bottom. But.tracks-page- wrapperhas no height (nomin-height: 100vh, noflex: 1against 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, so100%resolves to content height. Inert..tracks-content { display: flex }— wraps a single full-widthMudContainer; flex changes nothing.
Centering
.deepdrft-track-gallery-item-center (global §8) + MudGrid Justify="Justify.Center" (TracksGallery)
MudItembreakpoints. 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:
- Artist text (
.deepdrft-track-artist→ green-accent) - Genre chip (
MudChip Color="Color.Primary"→ filled green) - 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-subin 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.cssmust 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) at0,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(orflex: 1against a sized layout parent) to.tracks-page-wrapperso 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-wrapperflex-direction;.tracks-view-containerflex/flex:1;.tracks-contentdisplay:flex/flex-grow;.tracks-gallery-containerheight: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-chiptreatment — 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 existingopacity:.9; margin-top:4px. Because the treatment is theme-aware it stays in global §8 (ancestor-dependent). Likely also requires changing the RazorMudChipfromVariant.Filled Color.PrimarytoVariant.Outlined(orVariant.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-artistfrom green-accent to muted off-white in dark (rgba(250,250,248,.55), matching.deepdrft-track-metaor 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.Primarygreen. 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-fallbackbase 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-centerlocation — it is used by both the loaded path (TracksGallery) and the skeleton path (TracksView), so it correctly stays global. Leave in §8.
Suggested sequencing
- R1 + R4 + R3b together (one cleanup pass on the two scoped layout files — safe, no design risk).
- R5 (the design change — wants Daniel's eye on the result; the artist-demotion is a taste call).
- R2 only if Daniel elects to harden the cascade (R2a comment is near-free; R2b is the fuller fix).
Open questions for Daniel
- 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.
- 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)?
- 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)?