Merge p0-w2-tb-home: Home.razor wireframe rewrite, site footer, CurrentTrack

This commit is contained in:
Daniel Harvey
2026-05-17 22:01:58 -04:00
7 changed files with 1017 additions and 217 deletions
@@ -20,6 +20,17 @@
</MudContainer> </MudContainer>
<AudioPlayerBar /> <AudioPlayerBar />
</MudMainContent> </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> </AudioPlayerProvider>
</MudLayout> </MudLayout>
</div> </div>
@@ -18,3 +18,53 @@
right: 0.75rem; right: 0.75rem;
top: 0.5rem; 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 - split 50/50 *@
<section class="hero">
@* Hero Section *@ <div class="hero-left">
<MudPaper Elevation="0" Class="pa-8 mb-6 text-center deepdrft-gradient-hero deepdrft-hero-container"> <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"> <div class="hero-right">
<MudItem xs="12" md="8"> @* Pulsing rings *@
<MudText Typo="Typo.h1" <div class="circle-deco"></div>
Class="mb-4 deepdrft-text-hero deepdrft-hero-title"> <div class="circle-deco"></div>
DEEP DRFT <div class="circle-deco"></div>
</MudText>
<MudText Typo="Typo.h2" <div class="hero-right-content">
Class="mb-6 deepdrft-text-subtitle deepdrft-hero-subtitle"> @* Now-Playing card *@
ELECTRONIC MUSIC COLLECTIVE <div class="now-playing">
</MudText> <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" @if (Player?.IsLoaded == true)
Class="mb-8 deepdrft-text-description deepdrft-hero-text-container deepdrft-hero-description"> {
Live electronic music from Charleston, South Carolina <div class="waveform-bars">
</MudText> @* 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" @* Stat row - hard-coded for now. TODO Phase 2: wire to real track count / identity model. *@
Color="Color.Primary" <div class="hero-stat-row">
Size="Size.Large" <div class="hero-stat">
Class="mr-4 mb-2 deepdrft-hero-button-filled"> <div class="hero-stat-num">47+</div>
<MudIcon Icon="@Icons.Material.Filled.PlayArrow" Class="mr-2" /> <div class="hero-stat-label">Live Sessions</div>
START STREAMING </div>
</MudButton> <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" @* Divider *@
Variant="Variant.Outlined" <div class="section-divider">
Color="Color.Primary" <div class="divider-line"></div>
Size="Size.Large" <div class="divider-tag">The Sound</div>
Class="mb-2 deepdrft-hero-button-outlined"> <div class="divider-line"></div>
<MudIcon Icon="@Icons.Material.Filled.LibraryMusic" Class="mr-2" /> </div>
BROWSE TRACKS
</MudButton>
</MudItem>
</MudGrid>
</MudPaper>
@* About Section *@ @* Sound section *@
<MudGrid Class="mb-8" Spacing="6"> <section class="section">
<MudItem xs="12" md="6"> <div class="section-header">
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-primary deepdrft-border-left-secondary deepdrft-about-card"> <div>
<MudText Typo="Typo.h4" Color="Color.Secondary" Class="mb-4 deepdrft-text-bold"> <div class="section-label">Genres &amp; Moods</div>
<MudIcon Icon="@Icons.Material.Filled.Group" Class="mr-2" /> <h2 class="section-title">Every<br /><em>Frequency</em><br />Explored</h2>
The Collective </div>
</MudText> <p class="section-body">
<MudText Typo="Typo.body1" Class="mb-4 deepdrft-text-readable"> 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.
DeepDrft is a two-member electronic music collective based in Charleston, South Carolina. </p>
We create immersive soundscapes that blend the energy of underground club culture with </div>
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>
@* Features Section *@ <div class="genre-grid">
<MudPaper Elevation="2" Class="pa-8 mb-8 deepdrft-gradient-features"> @* TODO Phase 2.2: wire each genre to /genres/{slug} *@
<MudText Typo="Typo.h3" Align="Align.Center" Color="Color.Primary" Class="mb-8 deepdrft-text-bold"> <div class="genre-card">
Experience DeepDrft <div class="genre-name">House</div>
</MudText> <div class="genre-count">Foundation</div>
</div>
<MudGrid Spacing="6"> <div class="genre-card">
<MudItem xs="12" sm="6" md="3"> <div class="genre-name">Techno</div>
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-purple-tint"> <div class="genre-count">Architecture</div>
<MudIcon Icon="@Icons.Material.Filled.Headphones" </div>
Size="Size.Large" <div class="genre-card">
Color="Color.Secondary" <div class="genre-name">Trance</div>
Class="mb-3 deepdrft-icon-large" /> <div class="genre-count">Ascension</div>
<MudText Typo="Typo.h6" Color="Color.Secondary" Class="mb-2 deepdrft-text-bold"> </div>
High-Quality Streaming <div class="genre-card">
</MudText> <div class="genre-name">IDM</div>
<MudText Typo="Typo.body2"> <div class="genre-count">Intelligence</div>
Crystal-clear audio streaming with lossless quality for the best listening experience </div>
</MudText> <div class="genre-card">
</MudCard> <div class="genre-name">Progressive</div>
</MudItem> <div class="genre-count">Journey</div>
</div>
<MudItem xs="12" sm="6" md="3"> <div class="genre-card">
<MudCard Elevation="8" Class="pa-4 deepdrft-feature-card deepdrft-feature-icon-container deepdrft-card-pink-tint"> <div class="genre-name">Ambient</div>
<MudIcon Icon="@Icons.Material.Filled.LiveTv" <div class="genre-count">Atmosphere</div>
Size="Size.Large" </div>
Color="Color.Tertiary" </div>
Class="mb-3 deepdrft-icon-large" /> </section>
<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>
@* Location & Connect Section *@ @* Dark features section *@
<MudGrid Class="mb-8" Spacing="6"> <section class="section-dark">
<MudItem xs="12" md="6"> <div class="section-label section-label-dark">What We Offer</div>
<MudPaper Elevation="4" Class="pa-6 deepdrft-gradient-soft-accent deepdrft-border-top-quaternary"> <h2 class="section-title section-title-dark">
<MudText Typo="Typo.h4" Class="mb-4 deepdrft-text-bold deepdrft-text-quaternary"> Built for the<br /><em>Committed Listener</em>
<MudIcon Icon="@Icons.Material.Filled.LocationOn" Class="mr-2" /> </h2>
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>
@* Call to Action *@ <div class="features-grid">
<MudPaper Elevation="8" Class="pa-8 deepdrft-gradient-primary deepdrft-cta-container"> <div class="feature-card">
<MudText Typo="Typo.h3" Class="mb-4 deepdrft-text-bold deepdrft-hero-title"> <div class="feature-icon">
Ready to Dive Deep? <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>
</MudText> </div>
<MudText Typo="Typo.h6" Class="mb-6 deepdrft-text-description deepdrft-hero-description"> <div class="feature-title">Lossless Audio Streaming</div>
Immerse yourself in the electronic soundscapes of DeepDrft <div class="feature-desc">Crystal-clear sound delivered at full resolution. We don't compress the music &mdash; or the experience.</div>
</MudText> </div>
<MudButtonGroup Size="Size.Large" Class="deepdrft-cta-buttons"> <div class="feature-card">
<MudButton Variant="Variant.Filled" <div class="feature-icon">
Color="Color.Primary" <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" /><polygon points="10 8 16 12 10 16 10 8" /></svg>
Size="Size.Large" </div>
Class="deepdrft-hero-button-filled deepdrft-button-spaced"> <div class="feature-title">Live Sessions Broadcast</div>
<MudIcon Icon="@Icons.Material.Filled.PlayCircle" Class="mr-2" /> <div class="feature-desc">Join us in real time. Every session is unrepeatable &mdash; a singular moment sculpted between performer and listener.</div>
EXPLORE MUSIC </div>
</MudButton> <div class="feature-card">
<MudButton Variant="Variant.Outlined" <div class="feature-icon">
Color="Color.Primary" <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>
Size="Size.Large" </div>
Class="deepdrft-hero-button-outlined deepdrft-button-spaced"> <div class="feature-title">Studio Video Content</div>
<MudIcon Icon="@Icons.Material.Filled.Schedule" Class="mr-2" /> <div class="feature-desc">Behind-the-machine footage. Watch synthesis happen in real time, gear and all &mdash; no performance, just process.</div>
LIVE SCHEDULE </div>
</MudButton> <div class="feature-card">
</MudButtonGroup> <div class="feature-icon">
</MudPaper> <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 Volume { get; protected set; } = 0.8;
public double LoadProgress { get; protected set; } = 0; public double LoadProgress { get; protected set; } = 0;
public string? ErrorMessage { get; protected set; } 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 // Events
public EventCallback? OnStateChanged { get; set; } public EventCallback? OnStateChanged { get; set; }
@@ -68,12 +77,12 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
public virtual async Task SelectTrack(TrackEntity track) public virtual async Task SelectTrack(TrackEntity track)
{ {
await EnsureInitializedAsync(); await EnsureInitializedAsync();
await NotifyStateChanged(); await NotifyStateChanged();
if (OnTrackSelected.HasValue) if (OnTrackSelected.HasValue)
await OnTrackSelected.Value.InvokeAsync(); await OnTrackSelected.Value.InvokeAsync();
await LoadTrack(track); await LoadTrack(track);
await NotifyStateChanged(); await NotifyStateChanged();
} }
@@ -17,7 +17,8 @@ public interface IPlayerService
double Volume { get; } double Volume { get; }
double LoadProgress { get; } double LoadProgress { get; }
string? ErrorMessage { get; } string? ErrorMessage { get; }
TrackEntity? CurrentTrack { get; }
// Events for UI updates // Events for UI updates
EventCallback? OnStateChanged { get; set; } EventCallback? OnStateChanged { get; set; }
EventCallback? OnTrackSelected { get; set; } EventCallback? OnTrackSelected { get; set; }
@@ -70,6 +70,9 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
// Save track ID for seek operations // Save track ID for seek operations
_currentTrackId = track.EntryKey; _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 // Create new cancellation token for this streaming operation
_streamingCancellation = new CancellationTokenSource(); _streamingCancellation = new CancellationTokenSource();
@@ -434,6 +437,7 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
Duration = null; Duration = null;
LoadProgress = 0; LoadProgress = 0;
ErrorMessage = null; ErrorMessage = null;
CurrentTrack = null;
// 4. Reset streaming-specific state // 4. Reset streaming-specific state
IsStreamingMode = false; IsStreamingMode = false;