refactor(split): rename DeepDrftWeb -> DeepDrftPublic and DeepDrftWeb.Client -> DeepDrftPublic.Client (Phase 4)
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
@page "/"
|
||||
@rendermode InteractiveAuto
|
||||
@using DeepDrftPublic.Client.Services
|
||||
|
||||
<PageTitle>Deep DRFT - Electronic Music Collective</PageTitle>
|
||||
|
||||
@* 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 — 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>
|
||||
|
||||
<div class="hero-right">
|
||||
@* Pulsing rings *@
|
||||
<div class="circle-deco"></div>
|
||||
<div class="circle-deco"></div>
|
||||
<div class="circle-deco"></div>
|
||||
|
||||
<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>
|
||||
|
||||
@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>
|
||||
|
||||
@* 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">∞</div>
|
||||
<div class="hero-stat-label">Drift Points</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@* Divider *@
|
||||
<div class="section-divider">
|
||||
<div class="divider-line"></div>
|
||||
<div class="divider-tag">The Sound</div>
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
|
||||
@* Sound section *@
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<div>
|
||||
<div class="section-label">Genres & 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 — not a cage. Each session begins as something and ends as something else entirely.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
@* 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>
|
||||
|
||||
<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 — 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 — 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 — 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 — indexed, searchable, and always expanding.</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@* 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 — 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 & 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; }
|
||||
}
|
||||
@@ -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); }
|
||||
@@ -0,0 +1,42 @@
|
||||
@page "/tracks"
|
||||
@rendermode InteractiveAuto
|
||||
|
||||
<PageTitle>DeepDrft Track Gallery</PageTitle>
|
||||
|
||||
<div class="tracks-page-wrapper">
|
||||
<div class="tracks-view-container">
|
||||
@if (ViewModel.Page != null)
|
||||
{
|
||||
<div class="tracks-content">
|
||||
<TracksGallery Tracks="@ViewModel.Page.Items"
|
||||
SelectedTrack="_selectedTrack"
|
||||
SelectedTrackChanged="@PlayTrack"/>
|
||||
</div>
|
||||
<div class="tracks-footer py-4">
|
||||
<MudPagination Count="@ViewModel.Page.TotalPages"
|
||||
Selected="@ViewModel.Page.Page"
|
||||
SelectedChanged="@SetPage"
|
||||
BoundaryCount="2"
|
||||
MiddleCount="3"/>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="tracks-content">
|
||||
<MudGrid Spacing="3">
|
||||
@foreach (var i in Enumerable.Range(0, 12))
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="4" lg="3" xl="3">
|
||||
<div class="deepdrft-track-gallery-item-center">
|
||||
<MudSkeleton Width="240px" Height="240px" SkeletonType="SkeletonType.Rectangle"/>
|
||||
</div>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
</div>
|
||||
<div class="tracks-footer">
|
||||
<MudSkeleton Height="60px" Width="240px" Class="justify-center"/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,65 @@
|
||||
using DeepDrftModels.Entities;
|
||||
using DeepDrftPublic.Client.Services;
|
||||
using DeepDrftPublic.Client.ViewModels;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Models.Common;
|
||||
|
||||
namespace DeepDrftPublic.Client.Pages;
|
||||
|
||||
public partial class TracksView : ComponentBase
|
||||
{
|
||||
[Inject] public required TracksViewModel ViewModel { get; set; }
|
||||
[CascadingParameter] public required IPlayerService PlayerService { get; set; }
|
||||
|
||||
private TrackEntity? _selectedTrack = null;
|
||||
private int _clickCount = 0;
|
||||
private string _lifecycleStatus = "Not initialized";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_lifecycleStatus = "OnInitializedAsync called";
|
||||
await SetPage(1);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_lifecycleStatus = "OnAfterRenderAsync called - WebAssembly is active!";
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void TestInteractivity()
|
||||
{
|
||||
_clickCount++;
|
||||
_lifecycleStatus = $"Button clicked {_clickCount} times - Interactivity working!";
|
||||
}
|
||||
|
||||
private async Task SetPage(int newPage)
|
||||
{
|
||||
var result = await ViewModel.TrackClient.GetPage(newPage, ViewModel.PageSize, ViewModel.SortBy, ViewModel.IsDescending);
|
||||
|
||||
if (result is { Success: true, Value: PagedResult<TrackEntity> pageResult })
|
||||
{
|
||||
ViewModel.Page = pageResult;
|
||||
ViewModel.PageSize = pageResult.PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PlayTrack(TrackEntity? track)
|
||||
{
|
||||
if (track == null && _selectedTrack == null || track?.Id == _selectedTrack?.Id) return;
|
||||
|
||||
if (track is null)
|
||||
{
|
||||
await PlayerService.Unload();
|
||||
}
|
||||
else
|
||||
{
|
||||
await PlayerService.SelectTrack(track);
|
||||
}
|
||||
|
||||
_selectedTrack = track;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.tracks-page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tracks-view-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
padding: 0 16px; /* Horizontal padding only */
|
||||
}
|
||||
|
||||
.tracks-content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.tracks-footer {
|
||||
flex: 0 0;
|
||||
padding: 8px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
Reference in New Issue
Block a user