329 lines
16 KiB
Markdown
329 lines
16 KiB
Markdown
# Track Card Theming — align fallback + text with NowPlayingCard
|
||
|
||
Status: completed. Author: product-designer. Date: 2026-06-05. Implementer: maintenance-engineer. Landed 2026-06-05.
|
||
|
||
## Goal
|
||
|
||
Make the public tracks-page track cards match the NowPlayingCard's established visual
|
||
language:
|
||
|
||
- **Fallback background (no album art)** → navy-blue glassy look (translucent + blur),
|
||
as used by `.now-playing`.
|
||
- **Card text** → moss-green accent (`--deepdrft-green-accent` = `#3D7A68`) for the
|
||
label-grade text, matching `.np-label` / waveform bars.
|
||
|
||
## Scope constraint (read before implementing)
|
||
|
||
`TrackCard.razor` lives in **`DeepDrftShared.Client`**, which is consumed by **both** the
|
||
public site (light + dark) **and** the CMS host (`DeepDrftManager`, light-only). Any change
|
||
to TrackCard's defaults is inherited by the CMS. The NowPlayingCard aesthetic is a
|
||
*dark-on-dark* treatment; applying it unconditionally would break legibility on the CMS's
|
||
off-white surfaces.
|
||
|
||
**Therefore the fix must be theme-aware**, not a flat recolor. The dark/navy-glass +
|
||
moss-green treatment applies under `.deepdrft-theme-dark`; light/CMS keeps a legible
|
||
on-light treatment. The hooks below are written so a single CSS file carries both.
|
||
|
||
The card CSS lives in `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` section 8. That
|
||
stylesheet is served by the public host and (per its own header comment) is shared across
|
||
server and client of the public app. **It is not loaded by the CMS host.** This is
|
||
actually convenient: putting the navy-glass treatment in section 8 means it only reaches
|
||
the public site. The CMS gets TrackCard with no `.deepdrft-track-card-*` styling beyond
|
||
whatever MudBlazor defaults apply. Confirm during implementation that the CMS still
|
||
renders TrackCard legibly (it currently relies on the same classes; if the CMS does not
|
||
link this stylesheet, the fallback `mud-theme-secondary` is the only thing styling it).
|
||
If the CMS does not actually use TrackCard in any live page, this concern is moot — verify
|
||
before spending effort on the light path.
|
||
|
||
---
|
||
|
||
## 1. Current problems
|
||
|
||
### 1a. Fallback background resolves to white
|
||
|
||
`TrackCard.razor` line 11:
|
||
|
||
```razor
|
||
<MudPaper Class="deepdrft-track-card-fallback mud-theme-secondary" Elevation="0">
|
||
```
|
||
|
||
`mud-theme-secondary` paints the element with the theme's **Secondary** palette color.
|
||
In the dark palette (`DeepDrftPalettes.Dark`), `Secondary = "#FAFAF8"` (off-white). So a
|
||
track with no album art renders as a **white card** on the navy page — the opposite of the
|
||
intended navy-glass look, and it makes the white-intended text invisible-by-collision.
|
||
|
||
(In the light/CMS palette, Secondary = `#1A3C34` deep green — a dark card on a light page,
|
||
also not the intended look but at least legible. Either way `mud-theme-secondary` is the
|
||
wrong abstraction here.)
|
||
|
||
### 1b. All card text resolves to near-invisible navy
|
||
|
||
Every `MudText` in TrackCard uses `Color="Color.Surface"` (lines 20, 26, 36, 57). MudBlazor's
|
||
`Color.Surface` maps to the palette **Surface** color. In the dark palette,
|
||
`Surface = "#162437"` (navy-mid). So all text is navy-mid:
|
||
|
||
- On the *current buggy* white fallback: barely legible (low contrast dark-on-white, but
|
||
the white itself is wrong).
|
||
- On the *intended* navy-glass fallback: **near-invisible** (navy-mid text on a
|
||
navy-translucent ground — contrast well below WCAG AA).
|
||
- On a real album-art background (`.deepdrft-track-card-bg`, `brightness(0.7)`): unreliable,
|
||
depends entirely on the artwork.
|
||
|
||
`Color.Surface` is semantically "the color of a surface," never intended as a text color.
|
||
This is the root mistake.
|
||
|
||
### 1c. No per-component scoped CSS exists
|
||
|
||
There is **no** `TrackCard.razor.css`. All TrackCard styling is global in section 8 of
|
||
`deepdrft-styles.css` plus MudBlazor utility classes applied inline. The fix can stay in
|
||
section 8 (preferred — keeps the public-only scoping described above) rather than
|
||
introducing a scoped file.
|
||
|
||
---
|
||
|
||
## 2. Proposed changes
|
||
|
||
Two coordinated edits: (A) remove the wrong MudBlazor color utilities from the Razor so
|
||
CSS can own the colors, (B) add navy-glass + moss-green rules to section 8.
|
||
|
||
### 2a. Razor changes — `DeepDrftShared.Client/Components/TrackCard.razor`
|
||
|
||
Goal: stop hard-binding to palette Secondary/Surface; hand color control to CSS via stable
|
||
class hooks.
|
||
|
||
**Fallback `MudPaper` (line 11):** remove `mud-theme-secondary`. Replace the class list with
|
||
a single semantic hook:
|
||
|
||
```razor
|
||
<MudPaper Class="deepdrft-track-card-fallback" Elevation="0">
|
||
```
|
||
|
||
The navy-glass background moves into the `.deepdrft-track-card-fallback` CSS rule (2b).
|
||
|
||
**Text elements (lines 19–23, 25–29, 35–39, 57–60):** remove the `Color="Color.Surface"`
|
||
attribute from each `MudText`. Add class hooks so CSS can assign the moss-green / hierarchy
|
||
colors. Suggested hooks (match the NowPlayingCard's title/label/sub hierarchy):
|
||
|
||
- Track name (line 19, `Typo.subtitle1`) → add `deepdrft-track-title` to its `Class`.
|
||
Currently `Class="text-truncate mb-1"` → `Class="deepdrft-track-title text-truncate mb-1"`.
|
||
- Artist (line 25, `Typo.caption`) → add `deepdrft-track-artist`.
|
||
`Class="text-truncate mb-2"` → `Class="deepdrft-track-artist text-truncate mb-2"`.
|
||
- Album (line 35, `Typo.caption`) → add `deepdrft-track-meta`.
|
||
`Class="text-truncate"` → `Class="deepdrft-track-meta text-truncate"`.
|
||
- Release year (line 57, `Typo.caption`) → add `deepdrft-track-meta`.
|
||
Has no `Class` today → add `Class="deepdrft-track-meta"`.
|
||
|
||
Do **not** add inline `Style` or MudBlazor `Color` values; all color lives in CSS so the
|
||
theme-aware split works.
|
||
|
||
### 2b. CSS changes — `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` section 8
|
||
|
||
Reference values from NowPlayingCard (`NowPlayingCard.razor.css`):
|
||
|
||
- glass background: `rgba(250, 250, 248, 0.06)`
|
||
- glass border: `1px solid rgba(250, 250, 248, 0.12)`
|
||
- blur: `backdrop-filter: blur(8px)`
|
||
- label/accent text: `var(--deepdrft-green-accent)` (`#3D7A68`)
|
||
- title text: `var(--deepdrft-white)` (`#FAFAF8`)
|
||
- sub text: `rgba(250, 250, 248, 0.45)`
|
||
|
||
**Note:** NowPlayingCard's glass is a *light-tinted* translucency (`rgba(250,250,248,…)`)
|
||
over the navy page, which reads as navy-glass because the navy page shows through. To make
|
||
the fallback unambiguously "navy-blue glassy" even where the page behind it is not pure
|
||
navy (e.g. over a gradient), tint with navy explicitly. Use the navy token at low alpha
|
||
plus the same light border/blur. Both options below are acceptable; **Option A** matches
|
||
NowPlayingCard literally, **Option B** is more robustly navy. Recommend **Option B** for
|
||
the fallback since the card is a discrete object on a varied page, where NowPlayingCard
|
||
sits in a known navy context.
|
||
|
||
Replace the existing `.deepdrft-track-card-fallback` rule (currently only position/size)
|
||
and add the text rules. Proposed block (dark-mode treatment scoped under
|
||
`.deepdrft-theme-dark`; base rule keeps layout only):
|
||
|
||
```css
|
||
/* Fallback panel — layout (theme-agnostic) */
|
||
.deepdrft-track-card-fallback {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* Fallback panel — navy-glass treatment, dark theme only.
|
||
Mirrors NowPlayingCard's .now-playing glass. */
|
||
.deepdrft-theme-dark .deepdrft-track-card-fallback {
|
||
/* Option A (literal NowPlayingCard match):
|
||
background: rgba(250, 250, 248, 0.06); */
|
||
/* Option B (recommended — explicit navy tint): */
|
||
background: color-mix(in srgb, var(--deepdrft-navy) 55%, transparent);
|
||
border: 1px solid rgba(250, 250, 248, 0.12);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
/* Card text — dark theme. Moss-green for label-grade text, off-white title. */
|
||
.deepdrft-theme-dark .deepdrft-track-title {
|
||
color: var(--deepdrft-white);
|
||
}
|
||
.deepdrft-theme-dark .deepdrft-track-artist {
|
||
color: var(--deepdrft-green-accent);
|
||
}
|
||
.deepdrft-theme-dark .deepdrft-track-meta {
|
||
color: rgba(250, 250, 248, 0.55);
|
||
}
|
||
```
|
||
|
||
**Decision needed (1 of these):** the user said "track card text → the moss-green color."
|
||
Two readings:
|
||
|
||
1. **Title in moss-green, supporting text muted** — strongest match to "card text is the
|
||
moss green," puts the green on the most prominent line. Use:
|
||
`.deepdrft-track-title { color: var(--deepdrft-green-accent); }` and demote the artist
|
||
to off-white or muted.
|
||
2. **NowPlayingCard hierarchy** (title off-white `#FAFAF8`, accent green on the *label*,
|
||
sub muted) — the block above. This is the literal NowPlayingCard mapping: green is the
|
||
accent/label color, not the title color.
|
||
|
||
The block above implements a hybrid (artist line in green). **Recommend reading 1** — it
|
||
most directly satisfies "card text → moss green" and reads well on the navy-glass ground:
|
||
title in moss-green, artist + meta in muted off-white. If the user wants the literal
|
||
NowPlayingCard hierarchy instead, swap to reading 2. Flag this back to the user if
|
||
ambiguous; otherwise default to reading 1:
|
||
|
||
```css
|
||
/* Reading 1 (recommended default) */
|
||
.deepdrft-theme-dark .deepdrft-track-title { color: var(--deepdrft-green-accent); }
|
||
.deepdrft-theme-dark .deepdrft-track-artist { color: rgba(250, 250, 248, 0.70); }
|
||
.deepdrft-theme-dark .deepdrft-track-meta { color: rgba(250, 250, 248, 0.55); }
|
||
```
|
||
|
||
### 2c. Text legibility over real album art
|
||
|
||
When album art *is* present (`.deepdrft-track-card-bg`), the same text classes now apply.
|
||
Moss-green/off-white over arbitrary artwork at `brightness(0.7)` can still be low-contrast.
|
||
NowPlayingCard never sits over artwork, so it gives no precedent. Recommend a scrim behind
|
||
the content to guarantee contrast in both art and fallback cases:
|
||
|
||
```css
|
||
.deepdrft-track-card-content {
|
||
/* existing rules unchanged; add: */
|
||
background: linear-gradient(to top,
|
||
rgba(13, 27, 42, 0.75) 0%,
|
||
rgba(13, 27, 42, 0.35) 45%,
|
||
rgba(13, 27, 42, 0.0) 100%);
|
||
}
|
||
```
|
||
|
||
This is optional but recommended — it makes the green/off-white text legible over both the
|
||
fallback glass and any album art, and reinforces the navy identity. If the user prefers the
|
||
fallback glass to read cleaner without a scrim, gate the scrim to art-only by moving it to
|
||
`.deepdrft-track-card-bg` as an `::after` overlay instead. **Recommend the content-level
|
||
scrim** for uniformity. Confirm with the user if the glassy fallback should stay scrim-free.
|
||
|
||
### 2d. Light / CMS path
|
||
|
||
With the dark treatment scoped under `.deepdrft-theme-dark`, light mode currently gets **no**
|
||
explicit text color (the removed `Color.Surface`) and **no** fallback background beyond the
|
||
base layout rule. That means:
|
||
|
||
- Light fallback card → transparent (shows the page behind it). Likely undesirable.
|
||
- Light text → inherits MudBlazor body text (navy `#0D1B2A`), which is legible on a light
|
||
ground. Acceptable.
|
||
|
||
Add a minimal light treatment so the public site's light mode and the CMS don't regress to
|
||
a transparent fallback:
|
||
|
||
```css
|
||
.deepdrft-theme-light .deepdrft-track-card-fallback {
|
||
background: color-mix(in srgb, var(--deepdrft-navy) 8%, var(--deepdrft-white));
|
||
border: 1px solid var(--deepdrft-border);
|
||
}
|
||
```
|
||
|
||
Text in light mode can be left to inherit (navy on light reads fine) or, for consistency
|
||
with the green identity, set the title to `--deepdrft-green-accent` in light too — the
|
||
accent green has adequate contrast on a near-white card. Recommend leaving light text to
|
||
inherit unless the user wants the green identity carried into light mode.
|
||
|
||
**CMS caveat:** the CMS host does not link `deepdrft-styles.css` (it is in `DeepDrftPublic/wwwroot`).
|
||
If the CMS renders TrackCard, none of these rules reach it and the fallback will be
|
||
unstyled. Verify whether the CMS uses TrackCard at all before investing in the CMS path; if
|
||
it does, the light-mode rules need to live in the shared token/style layer the CMS *does*
|
||
load, not in `deepdrft-styles.css`. **This is the one open structural question** — resolve
|
||
it before implementing the light path.
|
||
|
||
---
|
||
|
||
## 3. Other theming issues spotted on the tracks page
|
||
|
||
Beyond the two the user called out:
|
||
|
||
### 3a. Genre chip uses `Color.Primary` — green-on-navy collision (dark)
|
||
|
||
`TrackCard.razor` line 48: `<MudChip Color="Color.Primary">`. In the dark palette
|
||
`Primary = "#3D7A68"` (the same moss-green we're about to use for text). A green chip sitting
|
||
next to green text on a navy-glass card flattens the hierarchy — everything is one green.
|
||
Recommend giving the chip its own treatment: filled navy-mid with green text/border, or a
|
||
subtle outlined variant, so it reads as a distinct tag rather than blending into the text.
|
||
Suggested: `Variant="Variant.Outlined"` with a `.deepdrft-genre-chip` color override to
|
||
`--deepdrft-green-accent` border + text on transparent. Low priority; raise with user.
|
||
|
||
### 3b. Play FAB uses `Color.Primary` — same green, but here it's correct
|
||
|
||
Line 67: `<MudFab Color="Color.Primary">`. Green FAB on navy is the intended interactive
|
||
accent (matches the dark palette's "green-accent is the primary interactive color" note in
|
||
`DeepDrftPalettes.Dark`). **No change** — this is the one place the green-as-primary mapping
|
||
is right. Noting it so the implementer doesn't "fix" it while touching the chip.
|
||
|
||
### 3c. Loading skeletons are theme-default gray
|
||
|
||
`TracksView.razor` lines 30, 37 use bare `MudSkeleton`. MudBlazor skeletons render in a
|
||
neutral gray that does not match the navy-glass language — on the navy dark page they'll be
|
||
light-gray rectangles, a jarring pre-load flash before the navy cards appear. Recommend
|
||
tinting skeletons toward the navy-glass treatment (`rgba(250,250,248,0.06)` pulse) so the
|
||
loading state previews the real cards. Low priority, but it's the most visible remaining
|
||
mismatch with the NowPlayingCard aesthetic on this page. Defer unless the user wants polish.
|
||
|
||
### 3d. Card container has no border / elevation language matching the glass
|
||
|
||
`.deepdrft-track-card-container` (section 8) sets size + `overflow: hidden` and relies on
|
||
MudCard `Elevation="4"` (a drop shadow). NowPlayingCard uses a **1px translucent border + no
|
||
shadow** for its flat-glass look. The track cards' Material drop-shadow is a different visual
|
||
vocabulary. For full alignment, consider `Elevation="0"` on the MudCard plus a
|
||
`1px solid rgba(250,250,248,0.12)` border on the container (dark) to match the glass edge.
|
||
Medium priority — this is what most distinguishes "Material card" from "NowPlayingCard glass
|
||
panel." Worth doing if the goal is genuine aesthetic match, not just color.
|
||
|
||
### 3e. `--deepdrft-green-accent` is not defined inside `.deepdrft-theme-dark`
|
||
|
||
The token block (`deepdrft-tokens.css`) defines `--deepdrft-green-accent: #3D7A68` only in
|
||
`:root`. The `.deepdrft-theme-dark` block re-declares the *alias* layer but inherits the raw
|
||
`--deepdrft-*` wireframe tokens from `:root`. NowPlayingCard already uses
|
||
`var(--deepdrft-green-accent)` successfully under dark, so the cascade resolves fine — **no
|
||
action needed**, just confirming the token is in scope for the new rules. Noting it so the
|
||
implementer doesn't worry the var is undefined in dark.
|
||
|
||
---
|
||
|
||
## 4. Summary of edits for the implementer
|
||
|
||
| File | Change | Priority |
|
||
|------|--------|----------|
|
||
| `DeepDrftShared.Client/Components/TrackCard.razor` | Drop `mud-theme-secondary` from fallback; drop `Color="Color.Surface"` from all 4 MudText; add class hooks `deepdrft-track-title` / `-artist` / `-meta` | required |
|
||
| `DeepDrftPublic/wwwroot/styles/deepdrft-styles.css` §8 | Navy-glass fallback (dark), moss-green/off-white text (dark), light fallback fallback-bg, optional content scrim | required |
|
||
| same §8 / §9 | Genre chip distinct treatment (3a) | optional |
|
||
| `DeepDrftShared.Client/Components/TrackCard.razor` | MudCard `Elevation="0"` + glass border on container (3d) | recommended for true match |
|
||
| `TracksView.razor` skeleton tint (3c) | polish | defer |
|
||
|
||
## 5. Open questions to resolve before / during implementation
|
||
|
||
1. **Green on title vs. green on label** (§2b reading 1 vs 2). Default to reading 1 (title
|
||
in moss-green) unless the user says otherwise.
|
||
2. **Does the CMS host render TrackCard, and does it link `deepdrft-styles.css`?** Determines
|
||
whether the light-mode rules belong in `deepdrft-styles.css` or the shared token layer.
|
||
This is the only blocker for the light path; the dark path (what the user actually asked
|
||
for) is unblocked.
|
||
3. **Scrim or no scrim** over album art (§2c). Recommend yes; confirm.
|
||
4. **Match the glass edge** (Elevation 0 + border, §3d) — confirm the user wants full
|
||
aesthetic match vs. color-only.
|