P0/W2/TB: rewrite Home to wireframe spec, add site footer, expose CurrentTrack; fix dead base writes, genre href scroll-to-top

This commit is contained in:
Daniel Harvey
2026-05-17 21:52:19 -04:00
parent 583ff26fd7
commit 3d94e45d0c
7 changed files with 1017 additions and 217 deletions
@@ -20,6 +20,17 @@
</MudContainer>
<AudioPlayerBar />
</MudMainContent>
<footer class="deepdrft-footer">
<div class="deepdrft-footer-logo">Deep <span>Drft</span></div>
<ul class="deepdrft-footer-links">
<li><a href="/tracks">Listen</a></li>
<li><a href="#">Sessions</a></li>
<li><a href="/tracks">Archive</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<div class="deepdrft-footer-copy">© 2026 Deep DRFT — Charleston, SC</div>
</footer>
</AudioPlayerProvider>
</MudLayout>
</div>
@@ -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);
}
+217 -213
View File
@@ -1,226 +1,230 @@
@page "/"
@page "/"
@using DeepDrftWeb.Client.Services
<PageTitle>Deep DRFT- Electronic Music Collective</PageTitle>
<PageTitle>Deep DRFT - Electronic Music Collective</PageTitle>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pa-0">
@* Hero Section *@
<MudPaper Elevation="0" Class="pa-8 mb-6 text-center deepdrft-gradient-hero deepdrft-hero-container">
@* Hero - split 50/50 *@
<section class="hero">
<div class="hero-left">
<div class="hero-eyebrow fade-up">Charleston, South Carolina</div>
<h1 class="hero-title fade-up">Deep<br /><em>Drft</em></h1>
<p class="hero-subtitle fade-up">Electronic Music Collective</p>
<p class="hero-desc fade-up">
We craft immersive electronic soundscapes &mdash; live, unscripted, and built from synthesizers, drum machines, and raw intention. No sets. No loops. Pure drift.
</p>
<div class="hero-actions fade-up">
<a class="btn-primary" href="/tracks">Start Streaming</a>
<a class="btn-ghost" href="/tracks">Browse Tracks</a>
</div>
</div>
<MudGrid Justify="Justify.Center">
<MudItem xs="12" md="8">
<MudText Typo="Typo.h1"
Class="mb-4 deepdrft-text-hero deepdrft-hero-title">
DEEP DRFT
</MudText>
<div class="hero-right">
@* Pulsing rings *@
<div class="circle-deco"></div>
<div class="circle-deco"></div>
<div class="circle-deco"></div>
<MudText Typo="Typo.h2"
Class="mb-6 deepdrft-text-subtitle deepdrft-hero-subtitle">
ELECTRONIC MUSIC COLLECTIVE
</MudText>
<div class="hero-right-content">
@* Now-Playing card *@
<div class="now-playing">
<div class="np-label"><span class="np-dot"></span>Now Playing</div>
<div class="np-title">@(Player?.CurrentTrack?.TrackName ?? "Nothing playing")</div>
<div class="np-sub">
@(Player?.CurrentTrack != null
? $"{Player.CurrentTrack.Artist} · {Player.CurrentTrack.Album ?? "Single"}"
: "Select a track to begin")
</div>
<MudText Typo="Typo.h3"
Class="mb-8 deepdrft-text-description deepdrft-hero-text-container deepdrft-hero-description">
Live electronic music from Charleston, South Carolina
</MudText>
@if (Player?.IsLoaded == true)
{
<div class="waveform-bars">
@* 20 bars - approximate the wireframe's varied animation timings *@
<div class="waveform-bar" style="--h-lo:6px;--h-hi:28px;--dur:0.7s;height:14px"></div>
<div class="waveform-bar" style="--h-lo:10px;--h-hi:36px;--dur:0.9s;height:28px"></div>
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:0.65s;height:10px"></div>
<div class="waveform-bar" style="--h-lo:12px;--h-hi:40px;--dur:1.1s;height:36px"></div>
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.8s;height:18px"></div>
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.75s;height:24px"></div>
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:0.95s;height:8px"></div>
<div class="waveform-bar" style="--h-lo:14px;--h-hi:42px;--dur:1.2s;height:32px"></div>
<div class="waveform-bar" style="--h-lo:6px;--h-hi:22px;--dur:0.68s;height:16px"></div>
<div class="waveform-bar" style="--h-lo:10px;--h-hi:38px;--dur:0.88s;height:30px"></div>
<div class="waveform-bar" style="--h-lo:4px;--h-hi:16px;--dur:0.72s;height:6px"></div>
<div class="waveform-bar" style="--h-lo:8px;--h-hi:30px;--dur:1.0s;height:20px"></div>
<div class="waveform-bar" style="--h-lo:12px;--h-hi:36px;--dur:0.85s;height:26px"></div>
<div class="waveform-bar" style="--h-lo:6px;--h-hi:24px;--dur:0.9s;height:14px"></div>
<div class="waveform-bar" style="--h-lo:10px;--h-hi:34px;--dur:0.78s;height:22px"></div>
<div class="waveform-bar" style="--h-lo:4px;--h-hi:20px;--dur:1.05s;height:12px"></div>
<div class="waveform-bar" style="--h-lo:14px;--h-hi:44px;--dur:0.92s;height:38px"></div>
<div class="waveform-bar" style="--h-lo:6px;--h-hi:26px;--dur:0.7s;height:18px"></div>
<div class="waveform-bar" style="--h-lo:8px;--h-hi:32px;--dur:0.82s;height:22px"></div>
<div class="waveform-bar" style="--h-lo:4px;--h-hi:18px;--dur:1.15s;height:10px"></div>
</div>
}
else
{
<div class="waveform-placeholder"></div>
}
</div>
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
Class="mr-4 mb-2 deepdrft-hero-button-filled">
<MudIcon Icon="@Icons.Material.Filled.PlayArrow" Class="mr-2" />
START STREAMING
</MudButton>
@* Stat row - hard-coded for now. TODO Phase 2: wire to real track count / identity model. *@
<div class="hero-stat-row">
<div class="hero-stat">
<div class="hero-stat-num">47+</div>
<div class="hero-stat-label">Live Sessions</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num">2</div>
<div class="hero-stat-label">Members</div>
</div>
<div class="hero-stat">
<div class="hero-stat-num">&infin;</div>
<div class="hero-stat-label">Drift Points</div>
</div>
</div>
</div>
</div>
</section>
<MudButton Href="/tracks"
Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Large"
Class="mb-2 deepdrft-hero-button-outlined">
<MudIcon Icon="@Icons.Material.Filled.LibraryMusic" Class="mr-2" />
BROWSE TRACKS
</MudButton>
</MudItem>
</MudGrid>
</MudPaper>
@* Divider *@
<div class="section-divider">
<div class="divider-line"></div>
<div class="divider-tag">The Sound</div>
<div class="divider-line"></div>
</div>
@* About Section *@
<MudGrid Class="mb-8" Spacing="6">
<MudItem xs="12" md="6">
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-primary deepdrft-border-left-secondary deepdrft-about-card">
<MudText Typo="Typo.h4" Color="Color.Secondary" Class="mb-4 deepdrft-text-bold">
<MudIcon Icon="@Icons.Material.Filled.Group" Class="mr-2" />
The Collective
</MudText>
<MudText Typo="Typo.body1" Class="mb-4 deepdrft-text-readable">
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.
</MudText>
<MudText Typo="Typo.body1" Class="deepdrft-text-readable">
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.
</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-secondary deepdrft-border-left-tertiary deepdrft-about-card">
<MudText Typo="Typo.h4" Color="Color.Tertiary" Class="mb-4 deepdrft-text-bold">
<MudIcon Icon="@Icons.Material.Filled.MusicNote" Class="mr-2" />
Our Sound
</MudText>
<MudChipSet ReadOnly T="string" Class="mb-4">
<MudChip Color="Color.Primary" Class="deepdrft-chip-spacing">House</MudChip>
<MudChip Color="Color.Secondary" Class="deepdrft-chip-spacing">Techno</MudChip>
<MudChip Color="Color.Tertiary" Class="deepdrft-chip-spacing">Trance</MudChip>
<MudChip Class="deepdrft-chip-spacing deepdrft-chip-quaternary">IDM</MudChip>
<MudChip Class="deepdrft-chip-spacing deepdrft-chip-senary">Progressive</MudChip>
<MudChip Class="deepdrft-chip-spacing deepdrft-chip-quinary">Ambient</MudChip>
</MudChipSet>
<MudText Typo="Typo.body1" Class="deepdrft-text-readable">
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.
</MudText>
</MudPaper>
</MudItem>
</MudGrid>
@* Sound section *@
<section class="section">
<div class="section-header">
<div>
<div class="section-label">Genres &amp; Moods</div>
<h2 class="section-title">Every<br /><em>Frequency</em><br />Explored</h2>
</div>
<p class="section-body">
From the hypnotic pulse of deep house to the fractal complexity of IDM, Deep DRFT refuses to be categorized. We treat genre as a palette &mdash; not a cage. Each session begins as something and ends as something else entirely.
</p>
</div>
@* Features Section *@
<MudPaper Elevation="2" Class="pa-8 mb-8 deepdrft-gradient-features">
<MudText Typo="Typo.h3" Align="Align.Center" Color="Color.Primary" Class="mb-8 deepdrft-text-bold">
Experience DeepDrft
</MudText>
<MudGrid Spacing="6">
<MudItem xs="12" sm="6" md="3">
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-purple-tint">
<MudIcon Icon="@Icons.Material.Filled.Headphones"
Size="Size.Large"
Color="Color.Secondary"
Class="mb-3 deepdrft-icon-large" />
<MudText Typo="Typo.h6" Color="Color.Secondary" Class="mb-2 deepdrft-text-bold">
High-Quality Streaming
</MudText>
<MudText Typo="Typo.body2">
Crystal-clear audio streaming with lossless quality for the best listening experience
</MudText>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-pink-tint">
<MudIcon Icon="@Icons.Material.Filled.LiveTv"
Size="Size.Large"
Color="Color.Tertiary"
Class="mb-3 deepdrft-icon-large" />
<MudText Typo="Typo.h6" Color="Color.Tertiary" Class="mb-2 deepdrft-text-bold">
Live Sessions
</MudText>
<MudText Typo="Typo.body2">
Join us for live streaming sessions and experience electronic music as it's created
</MudText>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-indigo-tint">
<MudIcon Icon="@Icons.Material.Filled.VideoLibrary"
Size="Size.Large"
Class="mb-3 deepdrft-icon-large deepdrft-text-quaternary" />
<MudText Typo="Typo.h6" Class="mb-2 deepdrft-text-bold deepdrft-text-quaternary">
Video Content
</MudText>
<MudText Typo="Typo.body2">
Watch behind-the-scenes content and live performance videos (coming soon)
</MudText>
</MudCard>
</MudItem>
<MudItem xs="12" sm="6" md="3">
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-lavender-tint">
<MudIcon Icon="@Icons.Material.Filled.Archive"
Size="Size.Large"
Class="mb-3 deepdrft-icon-large deepdrft-text-quinary" />
<MudText Typo="Typo.h6" Class="mb-2 deepdrft-text-bold deepdrft-text-quinary">
Growing Archive
</MudText>
<MudText Typo="Typo.body2">
Explore our expanding collection of tracks, mixes, and live recordings
</MudText>
</MudCard>
</MudItem>
</MudGrid>
</MudPaper>
<div class="genre-grid">
@* TODO Phase 2.2: wire each genre to /genres/{slug} *@
<div class="genre-card">
<div class="genre-name">House</div>
<div class="genre-count">Foundation</div>
</div>
<div class="genre-card">
<div class="genre-name">Techno</div>
<div class="genre-count">Architecture</div>
</div>
<div class="genre-card">
<div class="genre-name">Trance</div>
<div class="genre-count">Ascension</div>
</div>
<div class="genre-card">
<div class="genre-name">IDM</div>
<div class="genre-count">Intelligence</div>
</div>
<div class="genre-card">
<div class="genre-name">Progressive</div>
<div class="genre-count">Journey</div>
</div>
<div class="genre-card">
<div class="genre-name">Ambient</div>
<div class="genre-count">Atmosphere</div>
</div>
</div>
</section>
@* Location & Connect Section *@
<MudGrid Class="mb-8" Spacing="6">
<MudItem xs="12" md="6">
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-accent deepdrft-border-top-quaternary">
<MudText Typo="Typo.h4" Class="mb-4 deepdrft-text-bold deepdrft-text-quaternary">
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Class="mr-2" />
Charleston, SC
</MudText>
<MudText Typo="Typo.body1" Class="mb-4 deepdrft-text-readable">
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.
</MudText>
<MudText Typo="Typo.body1" Class="deepdrft-text-readable">
The Holy City's unique blend of tradition and innovation provides the perfect backdrop
for our electronic explorations, where historic charm meets futuristic beats.
</MudText>
</MudPaper>
</MudItem>
<MudItem xs="12" md="6">
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-tertiary deepdrft-border-top-senary">
<MudText Typo="Typo.h4" Class="mb-4 deepdrft-text-bold deepdrft-text-senary">
<MudIcon Icon="@Icons.Material.Filled.ConnectWithoutContact" Class="mr-2" />
Connect With Us
</MudText>
<MudText Typo="Typo.body1" Class="mb-4 deepdrft-text-readable">
Stay connected with DeepDrft for the latest releases, live session announcements,
and updates from our studio in Charleston.
</MudText>
<MudButtonGroup Variant="Variant.Outlined" Class="mb-2">
<MudButton StartIcon="@Icons.Material.Filled.Email" Color="Color.Primary">
Newsletter
</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Notifications" Color="Color.Tertiary">
Live Alerts
</MudButton>
</MudButtonGroup>
</MudPaper>
</MudItem>
</MudGrid>
@* Dark features section *@
<section class="section-dark">
<div class="section-label section-label-dark">What We Offer</div>
<h2 class="section-title section-title-dark">
Built for the<br /><em>Committed Listener</em>
</h2>
@* Call to Action *@
<MudPaper Elevation="8" Class="pa-8 deepdrft-gradient-primary deepdrft-cta-container">
<MudText Typo="Typo.h3" Class="mb-4 deepdrft-text-bold deepdrft-hero-title">
Ready to Dive Deep?
</MudText>
<MudText Typo="Typo.h6" Class="mb-6 deepdrft-text-description deepdrft-hero-description">
Immerse yourself in the electronic soundscapes of DeepDrft
</MudText>
<MudButtonGroup Size="Size.Large" Class="deepdrft-cta-buttons">
<MudButton Variant="Variant.Filled"
Color="Color.Primary"
Size="Size.Large"
Class="deepdrft-hero-button-filled deepdrft-button-spaced">
<MudIcon Icon="@Icons.Material.Filled.PlayCircle" Class="mr-2" />
EXPLORE MUSIC
</MudButton>
<MudButton Variant="Variant.Outlined"
Color="Color.Primary"
Size="Size.Large"
Class="deepdrft-hero-button-outlined deepdrft-button-spaced">
<MudIcon Icon="@Icons.Material.Filled.Schedule" Class="mr-2" />
LIVE SCHEDULE
</MudButton>
</MudButtonGroup>
</MudPaper>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24"><path d="M9 18V5l12-2v13" /><circle cx="6" cy="18" r="3" /><circle cx="18" cy="16" r="3" /></svg>
</div>
<div class="feature-title">Lossless Audio Streaming</div>
<div class="feature-desc">Crystal-clear sound delivered at full resolution. We don't compress the music &mdash; or the experience.</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /><polygon points="10 8 16 12 10 16 10 8" /></svg>
</div>
<div class="feature-title">Live Sessions Broadcast</div>
<div class="feature-desc">Join us in real time. Every session is unrepeatable &mdash; a singular moment sculpted between performer and listener.</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2" ry="2" /><line x1="8" y1="21" x2="16" y2="21" /><line x1="12" y1="17" x2="12" y2="21" /></svg>
</div>
<div class="feature-title">Studio Video Content</div>
<div class="feature-desc">Behind-the-machine footage. Watch synthesis happen in real time, gear and all &mdash; no performance, just process.</div>
</div>
<div class="feature-card">
<div class="feature-icon">
<svg viewBox="0 0 24 24"><path d="M3 3h18v18H3z" /><path d="M3 9h18M9 21V9" /></svg>
</div>
<div class="feature-title">Growing Archive</div>
<div class="feature-desc">A living catalogue of sessions, mixes, and experiments &mdash; indexed, searchable, and always expanding.</div>
</div>
</div>
</section>
</MudContainer>
@* Split: origin + connect *@
<div class="section-split">
<div class="split-left">
<div class="split-eyebrow">Our Origin</div>
<h2 class="split-title">Where the Holy City<br /><em>Meets the Future</em></h2>
<p class="split-body">
Charleston, South Carolina holds centuries of culture in its streets. We carry that weight into the studio &mdash; tradition as tension against the forward pull of electronic sound. The result is music that feels both ancient and unimagined.
</p>
</div>
<div class="split-right">
<div class="connect-label">Stay Connected</div>
<h2 class="connect-title">Never Miss<br />a <em>Session</em></h2>
<div class="connect-options">
@* TODO: wire to subscription system when newsletter/alerts backend exists *@
<div class="connect-option">
<div class="option-icon">
<svg viewBox="0 0 24 24"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /><polyline points="22,6 12,13 2,6" /></svg>
</div>
<div>
<div class="option-text-label">Newsletter</div>
<div class="option-text-sub">New releases &amp; studio dispatches</div>
</div>
</div>
<div class="connect-option">
<div class="option-icon">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" /><path d="M13.73 21a2 2 0 0 1-3.46 0" /></svg>
</div>
<div>
<div class="option-text-label">Live Alerts</div>
<div class="option-text-sub">Know the moment we go live</div>
</div>
</div>
</div>
@* TODO: subscription form lives behind this CTA *@
<button class="btn-primary connect-cta" type="button">Subscribe Free</button>
</div>
</div>
@* CTA banner *@
<section class="cta-banner">
<div class="cta-text">
<h2 class="cta-headline">Ready to<br /><em>Drift</em> Deeper?</h2>
<p class="cta-sub">Immerse yourself. The current is always running.</p>
</div>
<div class="cta-actions">
<a class="btn-white" href="/tracks">Explore the Archive</a>
@* TODO: route to /schedule when live-session schedule page exists *@
<button class="btn-outline-white" type="button">View Live Schedule</button>
</div>
</section>
@code {
[CascadingParameter] public IPlayerService? Player { get; set; }
}
+721
View File
@@ -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); }
@@ -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; }
/// <summary>
/// The currently selected track. In the streaming subclass this property is managed
/// exclusively by <see cref="StreamingAudioPlayerService"/>: set in
/// <c>LoadTrackStreaming</c> after <c>ResetToIdle</c> clears it, and cleared again
/// by <c>ResetToIdle</c> on stop/unload/dispose. Base-class subclasses that take the
/// <see cref="SelectTrack"/>/<see cref="Unload"/> path are responsible for managing
/// it themselves.
/// </summary>
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();
}
@@ -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; }
@@ -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;