From 3c7f28b2eb76026adbb3e70b4b85f8a5c65e31f7 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Mon, 15 Jun 2026 19:27:12 -0400 Subject: [PATCH 1/2] redesign(public): session detail as hero-overlay composition, NowPlaying-themed --- .../Pages/SessionDetail.razor | 137 ++++++++----- .../Pages/SessionDetail.razor.css | 180 ++++++++++++++++-- 2 files changed, 259 insertions(+), 58 deletions(-) diff --git a/DeepDrftPublic.Client/Pages/SessionDetail.razor b/DeepDrftPublic.Client/Pages/SessionDetail.razor index b83930d..f9d23af 100644 --- a/DeepDrftPublic.Client/Pages/SessionDetail.razor +++ b/DeepDrftPublic.Client/Pages/SessionDetail.razor @@ -1,5 +1,7 @@ @page "/sessions/{Id:long}" +@using DeepDrftModels.DTOs @using DeepDrftPublic.Client.Controls +@using DeepDrftPublic.Client.Services @inherits ReleaseDetailBase @(ViewModel.Release?.Title ?? "Session") - DeepDrft @@ -44,54 +46,103 @@ else var hasGenre = release.Genre is not null; var hasDate = release.ReleaseDate is not null; - - -
- @if (!string.IsNullOrEmpty(heroImage)) + + + + ← All sessions + + + @* The hero is the positioning context for every overlay row; the gradient shim and the + top/bottom overlays are absolutely positioned children of this wrapper. *@ +
+ @if (!string.IsNullOrEmpty(heroImage)) + { + + } + else + { + + + + } + + @* Darkening shim so overlaid text/controls stay legible over any image. *@ +
+ + @* Top overlay: secondary details (genre, release date) and the share affordance. *@ +
+ + @if (hasGenre) + { + + @release.Genre + + } + @if (hasDate) + { +
+ Released + @release.ReleaseDate!.Value.ToString("MMMM yyyy") +
+ } +
+ @if (ViewModel.Track is not null) { - - } - else - { - - - +
+ +
}
- @if (showCover) - { -
- -
- } - - - @if (hasGenre) - { -
- - @release.Genre - -
- } - @if (hasDate) - { -
- Released - @release.ReleaseDate!.Value.ToString("MMMM yyyy") -
- } -
- + + @* Bottom overlay: cover thumbnail, title/artist, and the play affordance in one row. *@ +
+ + @if (showCover) + { +
+ +
+ } +
+
@release.Title
+
@release.Artist
+
+ @if (ViewModel.Track is not null) + { +
+ +
+ } +
+
+
+ +
} @code { protected override string PersistKey => "session-detail"; + + [CascadingParameter] public IStreamingPlayerService? PlayerService { get; set; } + + // Mirrors the play-toggle wiring the shared scaffold owns. Session detail composes the player + // affordance directly (it diverges from ReleaseDetailScaffold for the overlay layout), so the + // toggle logic lives here: toggle if this track is already active, otherwise start a fresh stream. + private async Task PlayTrack() + { + var track = ViewModel.Track; + if (track is null || PlayerService is null) return; + + var isThisTrack = PlayerService.CurrentTrack?.Id == track.Id; + if (isThisTrack && (PlayerService.IsPlaying || PlayerService.IsPaused)) + { + await PlayerService.TogglePlayPause(); + } + else + { + await PlayerService.SelectTrackStreaming(track); + } + } } diff --git a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css index 59aa8b4..43551df 100644 --- a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css +++ b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css @@ -1,24 +1,174 @@ -/* Hero-dominant: a wide 16:9 image rather than the square cover used on track detail. */ -.session-detail-hero { - margin: 0 auto 2rem; - overflow: hidden; +/* Session detail is hero-dominant: a large background image with the detail components overlaid + directly on top, themed to match the NowPlaying glassmorphic family. The page widens to the + Large container (set in markup) rather than the shared 760px detail column. */ + +.session-detail-page { + padding-top: 2rem; + padding-bottom: 4rem; } -/* session-detail-hero-img rides on MudPaper (child Razor component); ::deep pierces its output. */ -::deep .session-detail-hero-img { +/* Positioning context for every overlay. A tall, dominant frame rather than the 16:9 strip. */ +.session-hero { + position: relative; width: 100%; - aspect-ratio: 16 / 9; + aspect-ratio: 16 / 10; + max-height: 70vh; + min-height: 420px; + margin-top: 1rem; + overflow: hidden; + border-radius: 8px; + box-shadow: 0 12px 40px color-mix(in srgb, var(--mud-palette-text-secondary) 22%, transparent); +} + +/* session-hero-img / session-hero-placeholder ride on MudPaper (child Razor component); + the class lands on MudPaper's native .mud-paper output, so ::deep pierces the component boundary. */ +::deep .session-hero-img { + position: absolute; + inset: 0; background-size: cover; background-position: center; background-repeat: no-repeat; } -/* Secondary medium square cover sitting below the hero — same 220px frame as mix detail, - smaller than the cut cover so the hero stays dominant. The cover-art MudPaper fills it. */ -.session-detail-cover { - aspect-ratio: 1 / 1; - max-width: 220px; - margin: 0 auto 2rem; - overflow: hidden; - box-shadow: 0 8px 28px color-mix(in srgb, var(--mud-palette-text-secondary) 18%, transparent); +::deep .session-hero-placeholder { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--mud-palette-surface); +} + +::deep .session-hero-placeholder .mud-icon-root { + font-size: 120px; +} + +/* Darkening gradient shim: stronger at the bottom (under the title/play row) and lighter toward + the middle, with a top darken so the genre/share overlay stays legible too. */ +.session-hero-shim { + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(to bottom, + rgba(0, 0, 0, 0.55) 0%, + rgba(0, 0, 0, 0.15) 28%, + rgba(0, 0, 0, 0.15) 55%, + rgba(0, 0, 0, 0.75) 100%); +} + +/* --- Top overlay: genre / date / share --- */ +.session-hero-top { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 2; + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 1rem; + padding: 1.25rem 1.5rem; +} + +.session-overlay-date { + display: flex; + flex-direction: column; + gap: 0.1rem; +} + +.session-overlay-label { + font-family: var(--deepdrft-font-mono); + font-size: 0.6rem; + letter-spacing: 0.25em; + text-transform: uppercase; + color: var(--deepdrft-green-accent); +} + +.session-overlay-value { + font-family: var(--deepdrft-font-body); + font-size: 0.8rem; + color: var(--deepdrft-white); +} + +/* Genre chip themed to the glassmorphic NowPlaying surface. The class lands on MudChip's native + .mud-chip output, so ::deep is required to reach it. */ +::deep .session-overlay-chip.mud-chip { + background: rgba(250, 250, 248, 0.06); + border: 1px solid rgba(250, 250, 248, 0.12); + backdrop-filter: blur(8px); + color: var(--deepdrft-white); + font-family: var(--deepdrft-font-mono); + letter-spacing: 0.12em; +} + +/* --- Bottom overlay: cover thumb / title / play --- */ +.session-hero-bottom { + position: absolute; + bottom: 0; + left: 0; + right: 0; + z-index: 2; + padding: 1.5rem; +} + +.session-cover-thumb { + flex: 0 0 auto; + width: 96px; + height: 96px; + overflow: hidden; + border: 1px solid rgba(250, 250, 248, 0.12); + box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45); +} + +.session-hero-titles { + flex: 1 1 auto; + min-width: 0; +} + +.session-overlay-title { + font-family: var(--deepdrft-font-display); + font-size: clamp(1.75rem, 4vw, 2.75rem); + font-weight: 400; + line-height: 1.1; + color: var(--deepdrft-white); + text-shadow: 0 2px 12px rgba(0, 0, 0, 0.6); +} + +.session-overlay-artist { + font-family: var(--deepdrft-font-body); + font-size: 0.85rem; + letter-spacing: 0.08em; + color: rgba(250, 250, 248, 0.7); + margin-top: 0.35rem; +} + +.session-hero-play { + flex: 0 0 auto; +} + +/* The play affordance and share button sit over a dark image — force their icon glyphs to the + light theme color regardless of MudBlazor's Secondary palette. Both PlayStateIcon and + SharePopover render MudIconButton / MudProgressCircular internals, so ::deep is required. */ +::deep .session-hero-play .mud-icon-button, +::deep .session-hero-play .mud-progress-circular, +::deep .session-hero-share .mud-icon-button { + color: var(--deepdrft-white); +} + +@media (max-width: 599.98px) { + .session-hero { + aspect-ratio: 3 / 4; + min-height: 380px; + } + + /* session-hero-bottom-row rides on MudStack's native output div, so ::deep is required. */ + ::deep .session-hero-bottom-row { + flex-wrap: wrap; + } + + .session-cover-thumb { + width: 72px; + height: 72px; + } } From 0758bfe7f14eab10af877867586f6d675708d4a5 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Mon, 15 Jun 2026 19:32:34 -0400 Subject: [PATCH 2/2] fix(css): add ::deep to .session-detail-page so Blazor isolation scope pierces MudContainer boundary --- DeepDrftPublic.Client/Pages/SessionDetail.razor.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css index 43551df..9bbe881 100644 --- a/DeepDrftPublic.Client/Pages/SessionDetail.razor.css +++ b/DeepDrftPublic.Client/Pages/SessionDetail.razor.css @@ -2,7 +2,7 @@ directly on top, themed to match the NowPlaying glassmorphic family. The page widens to the Large container (set in markup) rather than the shared 760px detail column. */ -.session-detail-page { +::deep .session-detail-page { padding-top: 2rem; padding-bottom: 4rem; }