docs: move Phase 8.6 from PLAN to COMPLETED, correct type labels to Studio/Live/Mix

This commit is contained in:
daniel-c-harvey
2026-06-11 18:49:58 -04:00
parent 6d3b9cd4d3
commit 0874042040
2 changed files with 227 additions and 222 deletions
+227
View File
@@ -8,6 +8,233 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
## Phase 8 — CMS Track Browser
### 8.6 "Music through Every Medium" home page section
**Landed:** 2026-06-12 on dev.
Replaces the "Genres & Moods" block in `DeepDrftPublic.Client/Pages/Home.razor` (lines 4386 — the `<section class="section">` containing the `.genre-grid`). The 6 text-only genre cards become **3 image-first cards** keyed on release format: Studio, Live, DJ Mix. The pivot is taxonomy → medium: instead of "what scene is this," the section answers "in what form does the music reach you."
The section-divider tag stays "The Sound." The `.section-divider` and `.section-header-grid` wrappers are **untouched** — only the header copy inside the grid and the card grid below it change. Everything from `.section-dark` onward is untouched.
**Design intent.** The current section is a flat, typographic palette grid — appropriate when the message was "we span many genres." The new message is fewer, weightier, photographic: three distinct *ways* the collective produces, each earning a full image pane. This trades the dense 6-up rhythm for a confident 3-up editorial spread, closer in spirit to the dark `.features-grid` (icon + title + desc) but image-led rather than icon-led. The card is the unit of interest now, not the grid texture.
### 1. Section header copy
| Slot | Class | Copy |
| --- | --- | --- |
| Label | `.section-label` | `Format & Medium` |
| Title | `.section-title` | `Music through<br /><em>Every</em><br />Medium` |
| Body | `.section-body` | `The same hands, three different rooms. A studio cut is built; a live set is risked; a DJ mix is woven. We release in every form the music asks for &mdash; each one a different relationship between the moment and the record of it.` |
The `<em>Every</em>` carries the italic-green emphasis the existing `.section-title em` rule already styles — no change needed there. (Title echoes the prior "Every Frequency Explored" cadence deliberately, so the replacement reads as an evolution of the same voice, not a rewrite.)
### 2. Card copy
| Card | Type label (`.medium-type`, mono) | Title (`.medium-name`, serif) | One-line descriptor (`.medium-desc`) |
| --- | --- | --- | --- |
| Studio | `Studio` | `Studio Releases` | `Composed, layered, and finished &mdash; tracks built to be returned to.` |
| Live | `Live` | `Live Releases` | `Performances caught in the moment, unrepeatable and unedited.` |
| DJ Mix | `Mix` | `DJ Mix Releases` | `Uninterrupted sets &mdash; one track bleeding into the next, start to finish.` |
The type labels (`Studio` / `Live` / `Mix`) play the same one-word-essence role the genre `.genre-count` labels did ("Foundation," "Architecture," …) — kept deliberately to preserve that tic of the original design.
### 3. HTML structure sketch
Replaces Home.razor lines 4386. Header grid block keeps its existing structure with only the copy swapped; the grid below is new:
```razor
@* Medium section *@
<section class="section">
<div class="section-header-grid">
<MudGrid Style="margin-bottom: 5rem;">
<MudItem xs="12" md="4">
<div class="section-label">Format &amp; Medium</div>
<h2 class="section-title">Music through<br /><em>Every</em><br />Medium</h2>
</MudItem>
<MudItem xs="12" md="8">
<p class="section-body"> ...body copy from §1... </p>
</MudItem>
</MudGrid>
</div>
<div class="medium-grid">
@* TODO Phase 3.x: wire each card to its format-filtered browse route once /tracks?format= exists *@
<div class="medium-card">
<div class="medium-image" style="background-image: url('img/dd-studio.jpg');">
<div class="medium-scrim"></div>
</div>
<div class="medium-body">
<div class="medium-type">Studio</div>
<div class="medium-name">Studio Releases</div>
<div class="medium-desc">Composed, layered, and finished &mdash; tracks built to be returned to.</div>
</div>
</div>
@* …Live card (dd-live.jpeg) and DJ Mix card (dd-dj.jpeg) follow the same shape… *@
</div>
</section>
```
Notes for the implementer:
- **Image as CSS `background-image`, not `<img>`.** This makes `cover`-cropping, the scrim overlay, and the hover scale trivial without a wrapper-overflow dance, and keeps these decorative-but-branded photos out of the document's content image flow. (If alt-text/SEO is later wanted, revisit — but these are mood images, not informational, so background is the right call here.) The card is one block: image pane on top, text body below, matching the brief's "image area + text below."
- The three cards are structurally identical — implementer can author one and repeat. Leave the `TODO` comment so the future format-filter routing has a home (mirrors the existing `@* TODO Phase 2.2 *@` convention in the current genre grid).
- Whether the card is a `<div>` or an `<a>` is deferred: there is no format-filtered route yet (the genre grid had the same unresolved `/genres/{slug}` TODO). Author as `<div>` now; the `.medium-card` hover styles already assume `cursor` affordance so promoting to `<a>` later is a one-line change.
### 4. CSS additions (`Home.razor.css`)
Added a new block after the (now-removed) genre-grid rules. New classes:
```css
/* ── MEDIUM GRID (Music through Every Medium) ── */
.medium-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--deepdrft-border);
border: 1px solid var(--deepdrft-border);
margin-bottom: 4rem;
}
.medium-card {
background: var(--deepdrft-white); /* fixed white ground — matches .section, see §9 */
cursor: pointer;
overflow: hidden; /* clips the hover image scale */
text-decoration: none;
display: block;
}
.medium-image {
position: relative;
width: 100%;
aspect-ratio: 4 / 3; /* consistent crop across all three; ~240px tall at 1-col, scales with column width */
background-size: cover;
background-position: center;
transition: transform 0.5s ease;
}
.medium-card:hover .medium-image { transform: scale(1.05); }
.medium-scrim {
position: absolute;
inset: 0;
background: linear-gradient(to bottom,
rgba(17, 35, 56, 0.0) 40%,
rgba(17, 35, 56, 0.35) 100%); /* navy scrim, weighted to the lower edge near the text seam */
transition: opacity 0.3s;
opacity: 0.7;
}
.medium-card:hover .medium-scrim { opacity: 1; }
.medium-body {
padding: 2rem 1.5rem;
position: relative;
}
/* Green underline sweep — same mechanic as the old .genre-card::after */
.medium-card::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 2px;
background: var(--deepdrft-green-accent);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s;
z-index: 1;
}
.medium-card:hover::after { transform: scaleX(1); }
.medium-type {
font-family: var(--deepdrft-font-mono);
font-size: 0.58rem;
letter-spacing: 0.2em;
color: var(--deepdrft-muted);
text-transform: uppercase;
margin-bottom: 0.6rem;
}
.medium-name {
font-family: var(--deepdrft-font-display);
font-size: 1.6rem;
font-weight: 400;
color: var(--deepdrft-navy);
margin-bottom: 0.75rem;
line-height: 1.1;
}
.medium-desc {
font-family: var(--deepdrft-font-body);
font-size: 0.82rem;
line-height: 1.65;
color: var(--deepdrft-navy);
opacity: 0.6;
}
```
Reuse decisions:
- `.section`, `.section-divider`, `.section-header-grid`, `.section-label`, `.section-title`, `.section-body` — all reused unchanged.
- `.medium-type` / `.medium-name` / `.medium-desc` are new but are deliberate near-clones of `.genre-count` / `.genre-name` (bumped from 1.5→1.6rem to suit the larger card) / a new descriptor line the genre cards never had. Kept as distinct classes rather than reusing the `.genre-*` names so the dead genre CSS can be removed cleanly.
- The underline-sweep `::after` is copied from `.genre-card::after` verbatim except for the added `z-index: 1` (needed because the card now has a stacking context from the image).
### 5. Responsive breakpoints
| Viewport | `.medium-grid` columns | Behaviour |
| --- | --- | --- |
| ≥ 960px | `repeat(3, 1fr)` | Three cards in a row — the primary editorial layout. |
| 600959px | `repeat(2, 1fr)` + third card spans both columns | Two on top, the third full-width below. Reads better than a lone 1-col orphan on tablet and keeps the image panes generous. |
| < 600px | `1fr` | Single column, cards stack. Each image pane is full content-width; `aspect-ratio: 4/3` keeps them generous (~260px tall at a typical mobile width). |
```css
@media (max-width: 959px) {
.medium-grid { grid-template-columns: repeat(2, 1fr); }
.medium-card:last-child { grid-column: 1 / -1; } /* third card spans full width */
}
@media (max-width: 599px) {
.medium-grid { grid-template-columns: 1fr; }
.medium-card:last-child { grid-column: auto; } /* reset the span at 1-col */
}
```
Note the breakpoint boundary is `959px` here (the existing genre grid used `960px` for its `max-width` query; `.section-header-grid` uses `min-width: 960px`). Using `max-width: 959px` avoids the 1px both-rules-fire overlap at exactly 960px. Implementer may keep `960` for consistency with the surrounding file if preferred — the `last-child` span makes the 960 edge case harmless either way.
### 6. Image placeholder names
All three in `DeepDrftPublic.Client/wwwroot/img/` (same dir as existing hero images), referenced as `img/<name>` to match the existing `Image1="img/..."` convention:
- `dd-studio.jpg`
- `dd-live.jpeg`
- `dd-dj.jpeg`
File extensions match existing photos on the page (`dd-duo-hero.jpeg`, `kp-shoulder-bw.jpeg`). Recommend source images at least 800px wide (rendered up to ~430px wide at the 3-col desktop layout on a 1440px viewport, so 800px covers 2× displays). Consistent landscape orientation across all three — the `4/3 aspect-ratio` crop will center-cover whatever is supplied, but landscape sources avoid heavy cropping.
### 7. Hover and overlay spec
- **Underline sweep** (preserved from genre cards): on `:hover`, a 2px green-accent bar sweeps in from the left along the card's bottom edge (`scaleX(0)→(1)`, 0.3s). Unchanged mechanic.
- **Image scale** (new, additive): on `:hover`, the background image scales to `1.05` over 0.5s, clipped by the card's `overflow: hidden`. Slow and subtle — a breath, not a zoom. This is the "parallax-scale" the brief allowed; pure CSS transform, no JS.
- **Scrim** (always-on, subtle): a navy gradient (`--deepdrft-navy` at 0%→35% alpha, top→bottom) sits over the image at `opacity: 0.7`, deepening to `1.0` on hover. Two jobs: (a) it weights the image toward its lower edge so the transition into the text body feels intentional rather than abrupt, and (b) it future-proofs for overlaying white text on the image if a later iteration wants the title *on* the photo. Today all text sits in `.medium-body` below the image, so the scrim is purely tonal — keep it light; it should never read as a dark box. If during implementation the supplied photos are already dark/low-key, dial the base opacity down to `0.4` rather than fighting them.
The hover bundle (underline + scale + scrim-deepen) fires together as one gesture. Don't stagger them.
### 8. Dark-mode awareness
The raw `--deepdrft-white` and `--deepdrft-navy` tokens are **literal** in both themes — they are *not* remapped under `.deepdrft-theme-dark` (verified in `deepdrft-tokens.css`; only the alias tokens like `--deepdrft-surface`/`--theme-*` flip). The existing `.section` and `.genre-card` both hardcode `background: var(--deepdrft-white)`, so **this whole section is a fixed off-white ground in both light and dark mode today** — it does not invert.
The new `.medium-card` follows that same convention deliberately: white card ground, navy text, in both themes. This keeps Phase 8.6 consistent with its untouched siblings (`.section` above it stays white; only `.section-dark` below it is dark). **Do not** introduce theme-aware surface tokens here — that would make this one section invert while the rest of the white `.section` stays put, which is a larger and out-of-scope design decision (if Daniel wants the public home page to genuinely respond to dark mode, that is a separate roadmap item spanning every `.section`, not a Phase 8.6 concern).
- **Images:** unaffected by theme — same assets render identically. The navy scrim also reads correctly against the off-white card in both modes.
- **Text & backgrounds:** `--deepdrft-navy` text on `--deepdrft-white` card in both modes. No `.deepdrft-theme-dark` overrides needed or wanted for this section.
### 9. Out of scope / deferred
- **Format-filtered routing.** Cards are non-navigating today (no `/tracks?format=` route exists). The `TODO` comment marks where it lands. This mirrors the genre grid's never-resolved `/genres/{slug}` TODO — don't build the route as part of 8.6.
- **A real format field on `TrackEntity`.** "Studio / Live / DJ Mix" is presentational copy here, not yet a data dimension. If these cards are ever to filter real tracks, the entity needs a `Format`/`ReleaseType` discriminator — that is Phase 3 (new content kinds) territory, not this cosmetic swap. Flagging so the copy isn't mistaken for an existing capability.
**Completion note:** "Genres & Moods" genre-card grid on home page replaced with "Music through Every Medium" 3-card section (Studio Releases, Live Releases, DJ Mix Releases), each image-led with background image, scrim overlay, hover scale+underline animations. Dead `.genre-*` CSS rules removed from `Home.razor.css`. New `.medium-*` CSS block added with responsive grid (3 cards at md+, 2-up at sm, single column at xs). Type labels corrected to `Studio / Live / Mix` (final decision superseding earlier spec). Three images (`dd-studio.jpg`, `dd-live.jpeg`, `dd-dj.jpeg`) added to `wwwroot/img/`.
---
### 8.6 CMS cache invalidation + orphaned release deletion (Wave 6)
**Landed:** 2026-06-12 on dev.
-222
View File
@@ -138,228 +138,6 @@ Three browse modes for the CMS `/tracks` page — **Track**, **Album**, **Genre*
---
## Phase 8.6 — "Music through Every Medium" Section
Replaces the "Genres & Moods" block in `DeepDrftPublic.Client/Pages/Home.razor` (current lines 4386 — the `<section class="section">` containing the `.genre-grid`). The 6 text-only genre cards become **3 image-first cards** keyed on release format: Studio, Live, DJ Mix. The pivot is taxonomy → medium: instead of "what scene is this," the section answers "in what form does the music reach you."
The section-divider tag stays "The Sound." The `.section-divider` and `.section-header-grid` wrappers (Home.razor lines 3657) are **untouched** — only the header copy inside the grid and the card grid below it change. Everything from `.section-dark` onward (line 88+) is untouched.
**Design intent.** The current section is a flat, typographic palette grid — appropriate when the message was "we span many genres." The new message is fewer, weightier, photographic: three distinct *ways* the collective produces, each earning a full image pane. This trades the dense 6-up rhythm for a confident 3-up editorial spread, closer in spirit to the dark `.features-grid` (icon + title + desc) but image-led rather than icon-led. The card is the unit of interest now, not the grid texture.
### 1. Section header copy
| Slot | Class | Copy |
| --- | --- | --- |
| Label | `.section-label` | `Format & Medium` |
| Title | `.section-title` | `Music through<br /><em>Every</em><br />Medium` |
| Body | `.section-body` | `The same hands, three different rooms. A studio cut is built; a live set is risked; a DJ mix is woven. We release in every form the music asks for &mdash; each one a different relationship between the moment and the record of it.` |
The `<em>Every</em>` carries the italic-green emphasis the existing `.section-title em` rule already styles — no change needed there. (Title echoes the prior "Every Frequency Explored" cadence deliberately, so the replacement reads as an evolution of the same voice, not a rewrite.)
### 2. Card copy
| Card | Type label (`.medium-type`, mono) | Title (`.medium-name`, serif) | One-line descriptor (`.medium-desc`) |
| --- | --- | --- | --- |
| Studio | `Produced` | `Studio Releases` | `Composed, layered, and finished &mdash; tracks built to be returned to.` |
| Live | `Captured` | `Live Releases` | `Performances caught in the moment, unrepeatable and unedited.` |
| DJ Mix | `Continuous` | `DJ Mix Releases` | `Uninterrupted sets &mdash; one track bleeding into the next, start to finish.` |
The type labels (`Produced` / `Captured` / `Continuous`) play the same one-word-essence role the genre `.genre-count` labels did ("Foundation," "Architecture," …) — kept deliberately to preserve that tic of the original design.
### 3. HTML structure sketch
Replaces Home.razor lines 4386. Header grid block (lines 4457) keeps its existing structure with only the copy swapped; the grid below is new:
```razor
@* Medium section *@
<section class="section">
<div class="section-header-grid">
<MudGrid Style="margin-bottom: 5rem;">
<MudItem xs="12" md="4">
<div class="section-label">Format &amp; Medium</div>
<h2 class="section-title">Music through<br /><em>Every</em><br />Medium</h2>
</MudItem>
<MudItem xs="12" md="8">
<p class="section-body"> ...body copy from §1... </p>
</MudItem>
</MudGrid>
</div>
<div class="medium-grid">
@* TODO Phase 3.x: wire each card to its format-filtered browse route once /tracks?format= exists *@
<div class="medium-card">
<div class="medium-image" style="background-image: url('img/medium-studio.jpeg');">
<div class="medium-scrim"></div>
</div>
<div class="medium-body">
<div class="medium-type">Produced</div>
<div class="medium-name">Studio Releases</div>
<div class="medium-desc">Composed, layered, and finished &mdash; tracks built to be returned to.</div>
</div>
</div>
@* …Live card (medium-live.jpeg) and DJ Mix card (medium-djmix.jpeg) follow the same shape… *@
</div>
</section>
```
Notes for the implementer:
- **Image as CSS `background-image`, not `<img>`.** This makes `cover`-cropping, the scrim overlay, and the hover scale trivial without a wrapper-overflow dance, and keeps these decorative-but-branded photos out of the document's content image flow. (If alt-text/SEO is later wanted, revisit — but these are mood images, not informational, so background is the right call here.) The card is one block: image pane on top, text body below, matching the brief's "image area + text below."
- The three cards are structurally identical — implementer can author one and repeat. Leave the `TODO` comment so the future format-filter routing has a home (mirrors the existing `@* TODO Phase 2.2 *@` convention in the current genre grid).
- Whether the card is a `<div>` or an `<a>` is deferred: there is no format-filtered route yet (the genre grid had the same unresolved `/genres/{slug}` TODO). Author as `<div>` now; the `.medium-card` hover styles already assume `cursor` affordance so promoting to `<a>` later is a one-line change.
### 4. CSS additions (`Home.razor.css`)
Add a new block after the genre-grid rules (lines 106165 can stay or be removed once the genre markup is gone — recommend **removing** the now-dead `.genre-grid` / `.genre-card` / `.genre-name` / `.genre-count` rules in the same change to avoid dead CSS, since nothing else on the page uses them; confirm no other consumer with a grep before deleting). New classes:
```css
/* ── MEDIUM GRID (Music through Every Medium) ── */
.medium-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: var(--deepdrft-border);
border: 1px solid var(--deepdrft-border);
margin-bottom: 4rem;
}
.medium-card {
background: var(--deepdrft-white); /* fixed white ground — matches .section, see §9 */
cursor: pointer;
overflow: hidden; /* clips the hover image scale */
text-decoration: none;
display: block;
}
.medium-image {
position: relative;
width: 100%;
aspect-ratio: 4 / 3; /* consistent crop across all three; ~240px tall at 1-col, scales with column width */
background-size: cover;
background-position: center;
transition: transform 0.5s ease;
}
.medium-card:hover .medium-image { transform: scale(1.05); }
.medium-scrim {
position: absolute;
inset: 0;
background: linear-gradient(to bottom,
rgba(17, 35, 56, 0.0) 40%,
rgba(17, 35, 56, 0.35) 100%); /* navy scrim, weighted to the lower edge near the text seam */
transition: opacity 0.3s;
opacity: 0.7;
}
.medium-card:hover .medium-scrim { opacity: 1; }
.medium-body {
padding: 2rem 1.5rem;
position: relative;
}
/* Green underline sweep — same mechanic as the old .genre-card::after */
.medium-card::after {
content: '';
position: absolute;
bottom: 0; left: 0; right: 0;
height: 2px;
background: var(--deepdrft-green-accent);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s;
z-index: 1;
}
.medium-card:hover::after { transform: scaleX(1); }
.medium-type {
font-family: var(--deepdrft-font-mono);
font-size: 0.58rem;
letter-spacing: 0.2em;
color: var(--deepdrft-muted);
text-transform: uppercase;
margin-bottom: 0.6rem;
}
.medium-name {
font-family: var(--deepdrft-font-display);
font-size: 1.6rem;
font-weight: 400;
color: var(--deepdrft-navy);
margin-bottom: 0.75rem;
line-height: 1.1;
}
.medium-desc {
font-family: var(--deepdrft-font-body);
font-size: 0.82rem;
line-height: 1.65;
color: var(--deepdrft-navy);
opacity: 0.6;
}
```
Reuse decisions:
- `.section`, `.section-divider`, `.section-header-grid`, `.section-label`, `.section-title`, `.section-body` — all reused unchanged.
- `.medium-type` / `.medium-name` / `.medium-desc` are new but are deliberate near-clones of `.genre-count` / `.genre-name` (bumped from 1.5→1.6rem to suit the larger card) / a new descriptor line the genre cards never had. Kept as distinct classes rather than reusing the `.genre-*` names so the dead genre CSS can be removed cleanly.
- The underline-sweep `::after` is copied from `.genre-card::after` verbatim except for the added `z-index: 1` (needed because the card now has a stacking context from the image).
### 5. Responsive breakpoints
| Viewport | `.medium-grid` columns | Behaviour |
| --- | --- | --- |
| ≥ 960px | `repeat(3, 1fr)` | Three cards in a row — the primary editorial layout. |
| 600959px | `repeat(2, 1fr)` + third card spans both columns | Two on top, the third full-width below. Reads better than a lone 1-col orphan on tablet and keeps the image panes generous. |
| < 600px | `1fr` | Single column, cards stack. Each image pane is full content-width; `aspect-ratio: 4/3` keeps them generous (~260px tall at a typical mobile width). |
```css
@media (max-width: 959px) {
.medium-grid { grid-template-columns: repeat(2, 1fr); }
.medium-card:last-child { grid-column: 1 / -1; } /* third card spans full width */
}
@media (max-width: 599px) {
.medium-grid { grid-template-columns: 1fr; }
.medium-card:last-child { grid-column: auto; } /* reset the span at 1-col */
}
```
Note the breakpoint boundary is `959px` here (the existing genre grid used `960px` for its `max-width` query; `.section-header-grid` uses `min-width: 960px`). Using `max-width: 959px` avoids the 1px both-rules-fire overlap at exactly 960px. Implementer may keep `960` for consistency with the surrounding file if preferred — the `last-child` span makes the 960 edge case harmless either way.
### 6. Image placeholder names
All three in `DeepDrftPublic.Client/wwwroot/img/` (same dir as existing hero images), referenced as `img/<name>` to match the existing `Image1="img/..."` convention:
- `medium-studio.jpeg`
- `medium-live.jpeg`
- `medium-djmix.jpeg`
`.jpeg` extension matches every existing photo on the page (`dd-duo-hero.jpeg`, `kp-shoulder-bw.jpeg`). Recommend source images at least 800px wide (rendered up to ~430px wide at the 3-col desktop layout on a 1440px viewport, so 800px covers 2× displays). Consistent landscape orientation across all three — the `4/3 aspect-ratio` crop will center-cover whatever is supplied, but landscape sources avoid heavy cropping.
### 7. Hover and overlay spec
- **Underline sweep** (preserved from genre cards): on `:hover`, a 2px green-accent bar sweeps in from the left along the card's bottom edge (`scaleX(0)→(1)`, 0.3s). Unchanged mechanic.
- **Image scale** (new, additive): on `:hover`, the background image scales to `1.05` over 0.5s, clipped by the card's `overflow: hidden`. Slow and subtle — a breath, not a zoom. This is the "parallax-scale" the brief allowed; pure CSS transform, no JS.
- **Scrim** (always-on, subtle): a navy gradient (`--deepdrft-navy` at 0%→35% alpha, top→bottom) sits over the image at `opacity: 0.7`, deepening to `1.0` on hover. Two jobs: (a) it weights the image toward its lower edge so the transition into the text body feels intentional rather than abrupt, and (b) it future-proofs for overlaying white text on the image if a later iteration wants the title *on* the photo. Today all text sits in `.medium-body` below the image, so the scrim is purely tonal — keep it light; it should never read as a dark box. If during implementation the supplied photos are already dark/low-key, dial the base opacity down to `0.4` rather than fighting them.
The hover bundle (underline + scale + scrim-deepen) fires together as one gesture. Don't stagger them.
### 8. Dark-mode awareness
The raw `--deepdrft-white` and `--deepdrft-navy` tokens are **literal** in both themes — they are *not* remapped under `.deepdrft-theme-dark` (verified in `deepdrft-tokens.css`; only the alias tokens like `--deepdrft-surface`/`--theme-*` flip). The existing `.section` and `.genre-card` both hardcode `background: var(--deepdrft-white)`, so **this whole section is a fixed off-white ground in both light and dark mode today** — it does not invert.
The new `.medium-card` follows that same convention deliberately: white card ground, navy text, in both themes. This keeps Phase 8.6 consistent with its untouched siblings (`.section` above it stays white; only `.section-dark` below it is dark). **Do not** introduce theme-aware surface tokens here — that would make this one section invert while the rest of the white `.section` stays put, which is a larger and out-of-scope design decision (if Daniel wants the public home page to genuinely respond to dark mode, that is a separate roadmap item spanning every `.section`, not a Phase 8.6 concern).
- **Images:** unaffected by theme — same `.jpeg` assets render identically. The navy scrim also reads correctly against the off-white card in both modes.
- **Text & backgrounds:** `--deepdrft-navy` text on `--deepdrft-white` card in both modes. No `.deepdrft-theme-dark` overrides needed or wanted for this section.
### 9. Out of scope / deferred
- **Format-filtered routing.** Cards are non-navigating today (no `/tracks?format=` route exists). The `TODO` comment marks where it lands. This mirrors the genre grid's never-resolved `/genres/{slug}` TODO — don't build the route as part of 8.6.
- **A real format field on `TrackEntity`.** "Studio / Live / DJ Mix" is presentational copy here, not yet a data dimension. If these cards are ever to filter real tracks, the entity needs a `Format`/`ReleaseType` discriminator — that is Phase 3 (new content kinds) territory, not this cosmetic swap. Flagging so the copy isn't mistaken for an existing capability.
---
A small set of items that are real but don't fit a phase yet. Surface them when they become relevant rather than committing now.