diff --git a/DeepDrftWeb.Client/Layout/MainLayout.razor b/DeepDrftWeb.Client/Layout/MainLayout.razor
index 29df2ae..56589fb 100644
--- a/DeepDrftWeb.Client/Layout/MainLayout.razor
+++ b/DeepDrftWeb.Client/Layout/MainLayout.razor
@@ -20,6 +20,17 @@
+
diff --git a/DeepDrftWeb.Client/Layout/MainLayout.razor.css b/DeepDrftWeb.Client/Layout/MainLayout.razor.css
index 60cec92..286b3ef 100644
--- a/DeepDrftWeb.Client/Layout/MainLayout.razor.css
+++ b/DeepDrftWeb.Client/Layout/MainLayout.razor.css
@@ -18,3 +18,53 @@
right: 0.75rem;
top: 0.5rem;
}
+
+/* ── SITE FOOTER ── */
+.deepdrft-footer {
+ background: var(--deepdrft-white);
+ border-top: 1px solid var(--deepdrft-border);
+ padding: 3rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 2rem;
+}
+
+.deepdrft-footer-logo {
+ font-family: var(--deepdrft-font-display);
+ font-size: 1.5rem;
+ font-weight: 400;
+ color: var(--deepdrft-navy);
+}
+
+.deepdrft-footer-logo span {
+ font-style: italic;
+ color: var(--deepdrft-green);
+}
+
+.deepdrft-footer-links {
+ display: flex;
+ gap: 2rem;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.deepdrft-footer-links a {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.62rem;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--deepdrft-muted);
+ text-decoration: none;
+ transition: color 0.2s;
+}
+
+.deepdrft-footer-links a:hover { color: var(--deepdrft-navy); }
+
+.deepdrft-footer-copy {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.58rem;
+ letter-spacing: 0.12em;
+ color: var(--deepdrft-muted);
+}
diff --git a/DeepDrftWeb.Client/Pages/Home.razor b/DeepDrftWeb.Client/Pages/Home.razor
index ebbbe92..d24385b 100644
--- a/DeepDrftWeb.Client/Pages/Home.razor
+++ b/DeepDrftWeb.Client/Pages/Home.razor
@@ -1,226 +1,230 @@
-@page "/"
+@page "/"
+@using DeepDrftWeb.Client.Services
-Deep DRFT- Electronic Music Collective
+Deep DRFT - Electronic Music Collective
-
-
- @* Hero Section *@
-
+@* Hero - split 50/50 *@
+
+
+
Charleston, South Carolina
+
DeepDrft
+
Electronic Music Collective
+
+ We craft immersive electronic soundscapes — live, unscripted, and built from synthesizers, drum machines, and raw intention. No sets. No loops. Pure drift.
+
+
+
-
-
-
- DEEP DRFT
-
+
+ @* Pulsing rings *@
+
+
+
-
- ELECTRONIC MUSIC COLLECTIVE
-
+
+ @* Now-Playing card *@
+
+
Now Playing
+
@(Player?.CurrentTrack?.TrackName ?? "Nothing playing")
+
+ @(Player?.CurrentTrack != null
+ ? $"{Player.CurrentTrack.Artist} · {Player.CurrentTrack.Album ?? "Single"}"
+ : "Select a track to begin")
+
-
- Live electronic music from Charleston, South Carolina
-
+ @if (Player?.IsLoaded == true)
+ {
+
+ }
+ else
+ {
+
+ }
+
-
-
- START STREAMING
-
+ @* Stat row - hard-coded for now. TODO Phase 2: wire to real track count / identity model. *@
+
+
+
+
-
-
- BROWSE TRACKS
-
-
-
-
+@* Divider *@
+
- @* About Section *@
-
-
-
-
-
- The Collective
-
-
- DeepDrft is a two-member electronic music collective based in Charleston, South Carolina.
- We create immersive soundscapes that blend the energy of underground club culture with
- the precision of live electronic performance.
-
-
- Using an arsenal of synthesizers, drum machines, and grooveboxes, we craft dynamic live
- performances that span house, techno, trance, and intelligent dance music (IDM).
- Every session is a journey through sound and rhythm.
-
-
-
-
-
-
-
-
- Our Sound
-
-
- House
- Techno
- Trance
- IDM
- Progressive
- Ambient
-
-
- From deep, driving basslines to ethereal atmospheric textures, our music explores the
- full spectrum of electronic expression. We believe in the power of live performance
- to create unique, unrepeatable moments that connect artist and audience through rhythm and melody.
-
-
-
-
+@* Sound section *@
+
+
- @* Features Section *@
-
-
- Experience DeepDrft
-
-
-
-
-
-
-
- High-Quality Streaming
-
-
- Crystal-clear audio streaming with lossless quality for the best listening experience
-
-
-
-
-
-
-
-
- Live Sessions
-
-
- Join us for live streaming sessions and experience electronic music as it's created
-
-
-
-
-
-
-
-
- Video Content
-
-
- Watch behind-the-scenes content and live performance videos (coming soon)
-
-
-
-
-
-
-
-
- Growing Archive
-
-
- Explore our expanding collection of tracks, mixes, and live recordings
-
-
-
-
-
+
+ @* TODO Phase 2.2: wire each genre to /genres/{slug} *@
+
+
+
Techno
+
Architecture
+
+
+
+
+
Progressive
+
Journey
+
+
+
+
- @* Location & Connect Section *@
-
-
-
-
-
- Charleston, SC
-
-
- Based in the vibrant music scene of Charleston, South Carolina, DeepDrft draws inspiration
- from both the city's rich cultural heritage and the cutting-edge sounds of global electronic music.
-
-
- The Holy City's unique blend of tradition and innovation provides the perfect backdrop
- for our electronic explorations, where historic charm meets futuristic beats.
-
-
-
-
-
-
-
-
- Connect With Us
-
-
- Stay connected with DeepDrft for the latest releases, live session announcements,
- and updates from our studio in Charleston.
-
-
-
- Newsletter
-
-
- Live Alerts
-
-
-
-
-
+@* Dark features section *@
+
+ What We Offer
+
+ Built for theCommitted Listener
+
- @* Call to Action *@
-
-
- Ready to Dive Deep?
-
-
- Immerse yourself in the electronic soundscapes of DeepDrft
-
-
-
-
- EXPLORE MUSIC
-
-
-
- LIVE SCHEDULE
-
-
-
+
+
+
+
Lossless Audio Streaming
+
Crystal-clear sound delivered at full resolution. We don't compress the music — or the experience.
+
+
+
+
Live Sessions Broadcast
+
Join us in real time. Every session is unrepeatable — a singular moment sculpted between performer and listener.
+
+
+
+
+
+
Studio Video Content
+
Behind-the-machine footage. Watch synthesis happen in real time, gear and all — no performance, just process.
+
+
+
+
Growing Archive
+
A living catalogue of sessions, mixes, and experiments — indexed, searchable, and always expanding.
+
+
+
-
+@* Split: origin + connect *@
+
+
+
Our Origin
+
Where the Holy CityMeets the Future
+
+ Charleston, South Carolina holds centuries of culture in its streets. We carry that weight into the studio — tradition as tension against the forward pull of electronic sound. The result is music that feels both ancient and unimagined.
+
+
+
+
Stay Connected
+
Never Miss a Session
+
+ @* TODO: wire to subscription system when newsletter/alerts backend exists *@
+
+
+
+
Newsletter
+
New releases & studio dispatches
+
+
+
+
+
+
Live Alerts
+
Know the moment we go live
+
+
+
+ @* TODO: subscription form lives behind this CTA *@
+
Subscribe Free
+
+
+
+@* CTA banner *@
+
+
+
Ready toDrift Deeper?
+
Immerse yourself. The current is always running.
+
+
+
Explore the Archive
+ @* TODO: route to /schedule when live-session schedule page exists *@
+
View Live Schedule
+
+
+
+@code {
+ [CascadingParameter] public IPlayerService? Player { get; set; }
+}
diff --git a/DeepDrftWeb.Client/Pages/Home.razor.css b/DeepDrftWeb.Client/Pages/Home.razor.css
new file mode 100644
index 0000000..46b97ad
--- /dev/null
+++ b/DeepDrftWeb.Client/Pages/Home.razor.css
@@ -0,0 +1,721 @@
+/* Home.razor scoped styles - wireframe-faithful marketing page. */
+
+/* ── Animations ── */
+@keyframes fade-up {
+ from { opacity: 0; transform: translateY(24px); }
+ to { opacity: 1; transform: none; }
+}
+
+@keyframes pulse-ring {
+ 0%, 100% { opacity: 0.15; transform: translate(-50%, -50%) scale(1); }
+ 50% { opacity: 0.4; transform: translate(-50%, -50%) scale(1.03); }
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.3; }
+}
+
+@keyframes wave-dance {
+ from { height: var(--h-lo, 4px); }
+ to { height: var(--h-hi, 20px); }
+}
+
+.fade-up {
+ opacity: 0;
+ animation: fade-up 0.8s ease forwards;
+}
+
+/* ── HERO ── */
+.hero {
+ min-height: 100vh;
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ overflow: hidden;
+}
+
+.hero-left {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ padding: 6rem 3rem;
+ position: relative;
+ background: var(--deepdrft-white);
+}
+
+.hero-eyebrow {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.65rem;
+ letter-spacing: 0.28em;
+ color: var(--deepdrft-green-accent);
+ text-transform: uppercase;
+ margin-bottom: 1.8rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ animation-delay: 0.1s;
+}
+
+.hero-eyebrow::before {
+ content: '';
+ display: block;
+ width: 2.5rem;
+ height: 1px;
+ background: var(--deepdrft-green-accent);
+}
+
+.hero-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(4.5rem, 8vw, 8.5rem);
+ font-weight: 300;
+ line-height: 0.92;
+ letter-spacing: -0.02em;
+ color: var(--deepdrft-navy);
+ margin-bottom: 0.5rem;
+ animation-delay: 0.22s;
+}
+
+.hero-title em {
+ font-style: italic;
+ color: var(--deepdrft-green);
+}
+
+.hero-subtitle {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(1rem, 2vw, 1.35rem);
+ font-weight: 300;
+ font-style: italic;
+ color: var(--deepdrft-muted);
+ margin-bottom: 3rem;
+ letter-spacing: 0.04em;
+ animation-delay: 0.34s;
+}
+
+.hero-desc {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.92rem;
+ line-height: 1.75;
+ color: var(--deepdrft-navy);
+ opacity: 0.7;
+ max-width: 36ch;
+ margin-bottom: 3rem;
+ animation-delay: 0.44s;
+}
+
+.hero-actions {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+ animation-delay: 0.54s;
+}
+
+.btn-primary {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--deepdrft-white);
+ background: var(--deepdrft-navy);
+ border: none;
+ padding: 1rem 2.2rem;
+ cursor: pointer;
+ text-decoration: none;
+ transition: background 0.25s, transform 0.2s;
+ display: inline-block;
+}
+
+.btn-primary:hover {
+ background: var(--deepdrft-green);
+ transform: translateY(-1px);
+}
+
+.btn-ghost {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--deepdrft-navy);
+ background: transparent;
+ border: 1px solid var(--deepdrft-border);
+ padding: 1rem 2.2rem;
+ cursor: pointer;
+ text-decoration: none;
+ transition: border-color 0.25s, color 0.25s;
+ display: inline-block;
+}
+
+.btn-ghost:hover { border-color: var(--deepdrft-navy); }
+
+.hero-right {
+ background: var(--deepdrft-navy);
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ overflow: hidden;
+}
+
+.circle-deco {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ border: 1px solid rgba(61, 122, 104, 0.3);
+ animation: pulse-ring 4s ease-in-out infinite;
+ pointer-events: none;
+}
+
+.circle-deco:nth-child(1) { width: 320px; height: 320px; animation-delay: 0s; }
+.circle-deco:nth-child(2) { width: 520px; height: 520px; animation-delay: 0.8s; }
+.circle-deco:nth-child(3) { width: 720px; height: 720px; animation-delay: 1.6s; }
+
+.hero-right-content {
+ position: relative;
+ z-index: 2;
+ padding: 3rem;
+}
+
+.now-playing {
+ background: rgba(250, 250, 248, 0.06);
+ border: 1px solid rgba(250, 250, 248, 0.12);
+ padding: 1.5rem;
+ margin-bottom: 1.5rem;
+ backdrop-filter: blur(8px);
+}
+
+.np-label {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.6rem;
+ letter-spacing: 0.25em;
+ color: var(--deepdrft-green-accent);
+ text-transform: uppercase;
+ margin-bottom: 0.75rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.np-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--deepdrft-green-accent);
+ animation: blink 1.2s ease-in-out infinite;
+}
+
+.np-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: 1.5rem;
+ font-weight: 400;
+ color: var(--deepdrft-white);
+ margin-bottom: 0.25rem;
+}
+
+.np-sub {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.75rem;
+ color: rgba(250, 250, 248, 0.45);
+ letter-spacing: 0.08em;
+}
+
+.waveform-bars {
+ display: flex;
+ align-items: center;
+ gap: 3px;
+ margin-top: 1.2rem;
+}
+
+.waveform-bar {
+ width: 3px;
+ background: var(--deepdrft-green-accent);
+ border-radius: 2px;
+ animation: wave-dance var(--dur, 1s) ease-in-out infinite alternate;
+}
+
+.waveform-placeholder {
+ margin-top: 1.2rem;
+ height: 1px;
+ background: rgba(250, 250, 248, 0.12);
+}
+
+.hero-stat-row {
+ display: flex;
+ gap: 1.5rem;
+}
+
+.hero-stat {
+ flex: 1;
+ background: rgba(250, 250, 248, 0.04);
+ border: 1px solid rgba(250, 250, 248, 0.08);
+ padding: 1.2rem;
+}
+
+.hero-stat-num {
+ font-family: var(--deepdrft-font-display);
+ font-size: 2rem;
+ font-weight: 300;
+ color: var(--deepdrft-white);
+ line-height: 1;
+}
+
+.hero-stat-label {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.58rem;
+ letter-spacing: 0.2em;
+ color: rgba(250, 250, 248, 0.4);
+ text-transform: uppercase;
+ margin-top: 0.4rem;
+}
+
+/* ── DIVIDER ── */
+.section-divider {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ padding: 2rem 3rem;
+ background: var(--deepdrft-white);
+}
+
+.divider-line {
+ flex: 1;
+ height: 1px;
+ background: var(--deepdrft-border);
+}
+
+.divider-tag {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.6rem;
+ letter-spacing: 0.25em;
+ color: var(--deepdrft-muted);
+ text-transform: uppercase;
+ white-space: nowrap;
+}
+
+/* ── SECTION (sound) ── */
+.section {
+ padding: 7rem 3rem;
+ background: var(--deepdrft-white);
+}
+
+.section-header {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+ gap: 4rem;
+ margin-bottom: 5rem;
+ align-items: end;
+}
+
+.section-label {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.62rem;
+ letter-spacing: 0.28em;
+ color: var(--deepdrft-green-accent);
+ text-transform: uppercase;
+ margin-bottom: 1.2rem;
+}
+
+.section-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(2.8rem, 5vw, 4.5rem);
+ font-weight: 300;
+ line-height: 1;
+ color: var(--deepdrft-navy);
+}
+
+.section-title em {
+ font-style: italic;
+ color: var(--deepdrft-green);
+}
+
+.section-body {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.9rem;
+ line-height: 1.8;
+ color: var(--deepdrft-navy);
+ opacity: 0.65;
+ max-width: 52ch;
+ align-self: end;
+}
+
+/* Genre grid */
+.genre-grid {
+ display: grid;
+ grid-template-columns: repeat(6, 1fr);
+ gap: 1px;
+ background: var(--deepdrft-border);
+ border: 1px solid var(--deepdrft-border);
+ margin-bottom: 4rem;
+}
+
+.genre-card {
+ background: var(--deepdrft-white);
+ padding: 2rem 1.5rem;
+ transition: background 0.3s, color 0.3s;
+ cursor: pointer;
+ position: relative;
+ overflow: hidden;
+ text-decoration: none;
+ display: block;
+}
+
+.genre-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;
+}
+
+.genre-card:hover::after { transform: scaleX(1); }
+.genre-card:hover { background: #f3f6f4; }
+
+.genre-name {
+ font-family: var(--deepdrft-font-display);
+ font-size: 1.5rem;
+ font-weight: 400;
+ color: var(--deepdrft-navy);
+ margin-bottom: 0.5rem;
+}
+
+.genre-count {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.58rem;
+ letter-spacing: 0.2em;
+ color: var(--deepdrft-muted);
+ text-transform: uppercase;
+}
+
+/* ── DARK FEATURE SECTION ── */
+.section-dark {
+ background: var(--deepdrft-navy);
+ padding: 7rem 3rem;
+ color: var(--deepdrft-white);
+}
+
+.section-label-dark {
+ color: var(--deepdrft-green-accent);
+}
+
+.section-title-dark {
+ color: var(--deepdrft-white);
+ margin-top: 0.5rem;
+}
+
+.section-title-dark em {
+ font-style: italic;
+ color: var(--deepdrft-green-accent);
+}
+
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 0;
+ border: 1px solid rgba(250, 250, 248, 0.08);
+ margin-top: 4rem;
+}
+
+.feature-card {
+ padding: 2.5rem;
+ border-right: 1px solid rgba(250, 250, 248, 0.08);
+ transition: background 0.3s;
+}
+
+.feature-card:last-child { border-right: none; }
+
+.feature-card:hover { background: rgba(250, 250, 248, 0.04); }
+
+.feature-icon {
+ width: 2.5rem;
+ height: 2.5rem;
+ border: 1px solid rgba(250, 250, 248, 0.15);
+ margin-bottom: 1.8rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.feature-icon svg {
+ width: 1rem;
+ height: 1rem;
+ stroke: var(--deepdrft-green-accent);
+ fill: none;
+ stroke-width: 1.5;
+}
+
+.feature-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: 1.3rem;
+ font-weight: 400;
+ color: var(--deepdrft-white);
+ margin-bottom: 0.8rem;
+ line-height: 1.2;
+}
+
+.feature-desc {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.8rem;
+ line-height: 1.7;
+ color: rgba(250, 250, 248, 0.45);
+}
+
+/* ── SPLIT: ORIGIN + CONNECT ── */
+.section-split {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ min-height: 60vh;
+}
+
+.split-left {
+ background: var(--deepdrft-green);
+ padding: 6rem 4rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ position: relative;
+ overflow: hidden;
+}
+
+.split-left::before {
+ content: '';
+ position: absolute;
+ top: -100px;
+ right: -100px;
+ width: 400px;
+ height: 400px;
+ border-radius: 50%;
+ background: rgba(61, 122, 104, 0.3);
+}
+
+.split-right {
+ padding: 6rem 4rem;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ background: var(--deepdrft-white);
+}
+
+.split-eyebrow {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.62rem;
+ letter-spacing: 0.28em;
+ color: rgba(250, 250, 248, 0.6);
+ text-transform: uppercase;
+ margin-bottom: 1.5rem;
+ position: relative;
+ z-index: 1;
+}
+
+.split-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(2.5rem, 4vw, 3.8rem);
+ font-weight: 300;
+ color: var(--deepdrft-white);
+ line-height: 1.05;
+ margin-bottom: 1.5rem;
+ position: relative;
+ z-index: 1;
+}
+
+.split-title em {
+ font-style: italic;
+ opacity: 0.65;
+}
+
+.split-body {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.88rem;
+ line-height: 1.8;
+ color: rgba(250, 250, 248, 0.6);
+ max-width: 42ch;
+ position: relative;
+ z-index: 1;
+}
+
+.connect-label {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.62rem;
+ letter-spacing: 0.28em;
+ color: var(--deepdrft-green-accent);
+ text-transform: uppercase;
+ margin-bottom: 1.5rem;
+}
+
+.connect-title {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(2rem, 3.5vw, 3rem);
+ font-weight: 300;
+ color: var(--deepdrft-navy);
+ line-height: 1.05;
+ margin-bottom: 2rem;
+}
+
+.connect-title em {
+ font-style: italic;
+ color: var(--deepdrft-green);
+}
+
+.connect-options {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ margin-bottom: 2.5rem;
+}
+
+.connect-option {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 1rem 1.25rem;
+ border: 1px solid var(--deepdrft-border);
+ cursor: pointer;
+ transition: border-color 0.25s, background 0.25s;
+ text-decoration: none;
+}
+
+.connect-option:hover {
+ border-color: var(--deepdrft-green-accent);
+ background: #f3f6f4;
+}
+
+.option-icon {
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--deepdrft-navy);
+ flex-shrink: 0;
+}
+
+.option-icon svg {
+ width: 0.9rem;
+ height: 0.9rem;
+ stroke: var(--deepdrft-white);
+ fill: none;
+ stroke-width: 1.5;
+}
+
+.option-text-label {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.65rem;
+ letter-spacing: 0.15em;
+ color: var(--deepdrft-navy);
+ text-transform: uppercase;
+}
+
+.option-text-sub {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.75rem;
+ color: var(--deepdrft-muted);
+ margin-top: 0.1rem;
+}
+
+.connect-cta {
+ align-self: flex-start;
+}
+
+/* ── CTA BANNER ── */
+.cta-banner {
+ background: var(--deepdrft-navy);
+ padding: 6rem 3rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 3rem;
+ position: relative;
+ overflow: hidden;
+}
+
+.cta-banner::before {
+ content: 'DRFT';
+ position: absolute;
+ right: -2rem;
+ top: 50%;
+ transform: translateY(-50%);
+ font-family: var(--deepdrft-font-display);
+ font-size: 22rem;
+ font-weight: 300;
+ color: rgba(250, 250, 248, 0.03);
+ line-height: 1;
+ pointer-events: none;
+ user-select: none;
+ letter-spacing: -0.05em;
+}
+
+.cta-text {
+ position: relative;
+ z-index: 1;
+}
+
+.cta-headline {
+ font-family: var(--deepdrft-font-display);
+ font-size: clamp(2.5rem, 5vw, 5rem);
+ font-weight: 300;
+ color: var(--deepdrft-white);
+ line-height: 1;
+ margin-bottom: 1rem;
+}
+
+.cta-headline em {
+ font-style: italic;
+ color: var(--deepdrft-green-accent);
+}
+
+.cta-sub {
+ font-family: var(--deepdrft-font-body);
+ font-size: 0.88rem;
+ color: rgba(250, 250, 248, 0.4);
+ letter-spacing: 0.05em;
+}
+
+.cta-actions {
+ display: flex;
+ gap: 1rem;
+ align-items: center;
+ position: relative;
+ z-index: 1;
+ flex-shrink: 0;
+}
+
+.btn-white {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--deepdrft-navy);
+ background: var(--deepdrft-white);
+ border: none;
+ padding: 1rem 2.2rem;
+ cursor: pointer;
+ text-decoration: none;
+ transition: background 0.25s, color 0.25s;
+ display: inline-block;
+}
+
+.btn-white:hover {
+ background: var(--deepdrft-green-accent);
+ color: var(--deepdrft-white);
+}
+
+.btn-outline-white {
+ font-family: var(--deepdrft-font-mono);
+ font-size: 0.68rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--deepdrft-white);
+ background: transparent;
+ border: 1px solid rgba(250, 250, 248, 0.3);
+ padding: 1rem 2.2rem;
+ cursor: pointer;
+ text-decoration: none;
+ transition: border-color 0.25s;
+ display: inline-block;
+}
+
+.btn-outline-white:hover { border-color: var(--deepdrft-white); }
diff --git a/DeepDrftWeb.Client/Services/AudioPlayerService.cs b/DeepDrftWeb.Client/Services/AudioPlayerService.cs
index 08151de..2118cc0 100644
--- a/DeepDrftWeb.Client/Services/AudioPlayerService.cs
+++ b/DeepDrftWeb.Client/Services/AudioPlayerService.cs
@@ -24,6 +24,15 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
public double Volume { get; protected set; } = 0.8;
public double LoadProgress { get; protected set; } = 0;
public string? ErrorMessage { get; protected set; }
+ ///
+ /// The currently selected track. In the streaming subclass this property is managed
+ /// exclusively by : set in
+ /// LoadTrackStreaming after ResetToIdle clears it, and cleared again
+ /// by ResetToIdle on stop/unload/dispose. Base-class subclasses that take the
+ /// / path are responsible for managing
+ /// it themselves.
+ ///
+ public TrackEntity? CurrentTrack { get; protected set; }
// Events
public EventCallback? OnStateChanged { get; set; }
@@ -68,12 +77,12 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
public virtual async Task SelectTrack(TrackEntity track)
{
await EnsureInitializedAsync();
-
+
await NotifyStateChanged();
-
+
if (OnTrackSelected.HasValue)
await OnTrackSelected.Value.InvokeAsync();
-
+
await LoadTrack(track);
await NotifyStateChanged();
}
diff --git a/DeepDrftWeb.Client/Services/IPlayerService.cs b/DeepDrftWeb.Client/Services/IPlayerService.cs
index 120af1e..acbd2ff 100644
--- a/DeepDrftWeb.Client/Services/IPlayerService.cs
+++ b/DeepDrftWeb.Client/Services/IPlayerService.cs
@@ -17,7 +17,8 @@ public interface IPlayerService
double Volume { get; }
double LoadProgress { get; }
string? ErrorMessage { get; }
-
+ TrackEntity? CurrentTrack { get; }
+
// Events for UI updates
EventCallback? OnStateChanged { get; set; }
EventCallback? OnTrackSelected { get; set; }
diff --git a/DeepDrftWeb.Client/Services/StreamingAudioPlayerService.cs b/DeepDrftWeb.Client/Services/StreamingAudioPlayerService.cs
index d6eb8d7..c5a7279 100644
--- a/DeepDrftWeb.Client/Services/StreamingAudioPlayerService.cs
+++ b/DeepDrftWeb.Client/Services/StreamingAudioPlayerService.cs
@@ -70,6 +70,9 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
// Save track ID for seek operations
_currentTrackId = track.EntryKey;
+ // Expose to UI immediately — Now-Playing surfaces should reflect the selected
+ // track while it's still loading, not only after playback starts.
+ CurrentTrack = track;
// Create new cancellation token for this streaming operation
_streamingCancellation = new CancellationTokenSource();
@@ -434,6 +437,7 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
Duration = null;
LoadProgress = 0;
ErrorMessage = null;
+ CurrentTrack = null;
// 4. Reset streaming-specific state
IsStreamingMode = false;