Fix VinylPanel layout and styling to match Figma design

- Correct z-ordering: cover in front (z-10), disc behind
- Cover slides left when playing to reveal disc
- Add mouse parallax effect for depth
- More visible grooves with alternating brightness
- Neutral gray colors instead of amber vintage
- Use system typography instead of Oswald
- Radial gradient on disc for realistic shading
This commit is contained in:
vicrodh committed 2026-02-01 13:52:26 -06:00
1 parent d79fdf58b6
commit fcb81dad2b
1 file changed
+182 -215
@@ -23,69 +23,88 @@
samplingRate samplingRate
}: Props = $props(); }: Props = $props();
// Generate groove rings (40 rings like Figma design) // Mouse position for parallax effect
let mouseX = $state(0.5);
let mouseY = $state(0.5);
let containerRef: HTMLDivElement | null = $state(null);
function handleMouseMove(e: MouseEvent) {
if (!containerRef) return;
const rect = containerRef.getBoundingClientRect();
mouseX = (e.clientX - rect.left) / rect.width;
mouseY = (e.clientY - rect.top) / rect.height;
}
function handleMouseLeave() {
mouseX = 0.5;
mouseY = 0.5;
}
// Parallax offset calculations
const coverOffsetX = $derived((mouseX - 0.5) * 15);
const coverOffsetY = $derived((mouseY - 0.5) * 10);
const discOffsetX = $derived((mouseX - 0.5) * -8);
const discOffsetY = $derived((mouseY - 0.5) * -5);
// Generate groove rings (40 rings)
const grooveCount = 40; const grooveCount = 40;
const grooves = Array.from({ length: grooveCount }, (_, i) => i); const grooves = Array.from({ length: grooveCount }, (_, i) => i);
</script> </script>
<div class="vinyl-panel"> <div
<div class="vinyl-content"> class="vinyl-panel"
<!-- Album Cover (left side) --> bind:this={containerRef}
<div class="album-cover" class:playing={isPlaying}> onmousemove={handleMouseMove}
<img src={artwork} alt={trackTitle} /> onmouseleave={handleMouseLeave}
</div> >
<div class="vinyl-container" class:playing={isPlaying}>
<!-- Spinning Vinyl Disc --> <!-- Vinyl Disc (BEHIND the cover) -->
<div class="vinyl-disc-container"> <div
class="vinyl-disc-wrapper"
style="transform: translate({discOffsetX}px, {discOffsetY}px)"
>
<div class="vinyl-disc" class:spinning={isPlaying}> <div class="vinyl-disc" class:spinning={isPlaying}>
<!-- Grooves --> <!-- Grooves -->
{#each grooves as i} {#each grooves as i}
<div <div
class="groove" class="groove"
style="transform: scale({0.3 + i * 0.015})" style="transform: scale({0.28 + i * 0.017})"
></div> ></div>
{/each} {/each}
<!-- Center Label --> <!-- Center Label -->
<div class="center-label"> <div class="center-label">
<div class="label-inner">
<div class="spindle"></div> <div class="spindle"></div>
</div> </div>
</div>
<!-- Reflection --> <!-- Light reflection -->
<div class="reflection"></div> <div class="reflection"></div>
</div> </div>
</div> </div>
<!-- Track Info (right side) --> <!-- Album Cover (IN FRONT, slides to reveal disc) -->
<div class="track-info" class:playing={isPlaying}> <div
<!-- Vintage Badge --> class="album-cover"
<div class="vintage-badge"> class:revealed={isPlaying}
<span>33&#8531; RPM &bull; Stereo</span> style="transform: translate({coverOffsetX}px, {coverOffsetY}px)"
>
<img src={artwork} alt={trackTitle} />
</div>
</div> </div>
<!-- Track Info (below) -->
<div class="track-info">
<h1 class="track-title">{trackTitle}</h1> <h1 class="track-title">{trackTitle}</h1>
<p class="track-artist">{artist}</p> <p class="track-artist">{artist}</p>
{#if album} {#if album}
<p class="track-album">{album}</p> <p class="track-album">{album}</p>
{/if} {/if}
<!-- Vintage Details -->
<div class="vintage-details">
<div class="detail-row">
<span class="label">Side</span>
<span class="value">A</span>
</div>
<div class="detail-row">
<span class="label">Format</span>
<span class="value">12" Vinyl LP</span>
</div>
</div>
<div class="quality-badge-wrapper"> <div class="quality-badge-wrapper">
<QualityBadge {quality} {bitDepth} {samplingRate} /> <QualityBadge {quality} {bitDepth} {samplingRate} />
</div> </div>
</div> </div>
</div>
</div> </div>
<style> <style>
@@ -93,6 +112,7 @@
position: absolute; position: absolute;
inset: 0; inset: 0;
display: flex; display: flex;
flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding-top: 70px; padding-top: 70px;
@@ -100,111 +120,120 @@
padding-left: 40px; padding-left: 40px;
padding-right: 40px; padding-right: 40px;
z-index: 5; z-index: 5;
gap: 24px;
} }
.vinyl-content { .vinyl-container {
position: relative;
width: min(500px, 55vh);
height: min(500px, 55vh);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0;
position: relative;
width: min(900px, 90vw);
height: min(450px, 50vh);
} }
/* Album Cover */ /* Vinyl Disc - positioned behind cover */
.album-cover { .vinyl-disc-wrapper {
position: absolute; position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%) translateX(80px);
width: min(340px, 38vh);
height: min(340px, 38vh);
border-radius: 8px;
overflow: hidden;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.6),
0 20px 60px rgba(0, 0, 0, 0.4);
z-index: 10;
transition: transform 500ms cubic-bezier(0.23, 1, 0.32, 1);
}
.album-cover.playing {
transform: translateY(-50%) translateX(0);
}
.album-cover img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; transition: transform 150ms ease-out;
}
/* Vinyl Disc Container */
.vinyl-disc-container {
position: absolute;
right: 60px;
top: 50%;
transform: translateY(-50%);
width: min(400px, 45vh);
height: min(400px, 45vh);
} }
.vinyl-disc { .vinyl-disc {
position: relative; position: absolute;
width: 100%; top: 50%;
height: 100%; left: 50%;
transform: translate(-50%, -50%);
width: 95%;
height: 95%;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, #1a1a1a 0%, #000 50%, #1a1a1a 100%); background: radial-gradient(
circle at 30% 30%,
#2a2a2a 0%,
#0a0a0a 40%,
#1a1a1a 70%,
#0a0a0a 100%
);
box-shadow: box-shadow:
0 8px 32px rgba(0, 0, 0, 0.5), 0 10px 40px rgba(0, 0, 0, 0.6),
0 20px 60px rgba(0, 0, 0, 0.3), 0 20px 60px rgba(0, 0, 0, 0.4),
inset 0 0 60px rgba(0, 0, 0, 0.8); inset 0 0 80px rgba(0, 0, 0, 0.9);
} }
.vinyl-disc.spinning { .vinyl-disc.spinning {
animation: spin 3s linear infinite; animation: spin 2.5s linear infinite;
} }
@keyframes spin { @keyframes spin {
from { transform: rotate(0deg); } from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: rotate(360deg); } to { transform: translate(-50%, -50%) rotate(360deg); }
} }
/* Grooves */ /* Grooves - more visible */
.groove { .groove {
position: absolute; position: absolute;
inset: 0; top: 50%;
left: 50%;
transform-origin: center center;
width: 100%;
height: 100%;
margin-left: -50%;
margin-top: -50%;
border-radius: 50%; border-radius: 50%;
border: 1px solid rgba(255, 255, 255, 0.04); border: 1px solid rgba(255, 255, 255, 0.08);
pointer-events: none; pointer-events: none;
} }
/* Alternating groove brightness for more realism */
.groove:nth-child(odd) {
border-color: rgba(255, 255, 255, 0.05);
}
.groove:nth-child(even) {
border-color: rgba(255, 255, 255, 0.1);
}
.groove:nth-child(3n) {
border-color: rgba(255, 255, 255, 0.12);
}
/* Center Label */ /* Center Label */
.center-label { .center-label {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 100px; width: 28%;
height: 100px; height: 28%;
border-radius: 50%; border-radius: 50%;
background: linear-gradient(135deg, #b45309 0%, #78350f 100%); background: linear-gradient(135deg, #3a3a3a 0%, #1a1a1a 100%);
border: 4px solid #92400e; border: 3px solid #2a2a2a;
display: flex;
align-items: center;
justify-content: center;
box-shadow:
inset 0 2px 10px rgba(0, 0, 0, 0.5),
0 2px 10px rgba(0, 0, 0, 0.3);
}
.label-inner {
width: 70%;
height: 70%;
border-radius: 50%;
background: linear-gradient(135deg, #4a4a4a 0%, #2a2a2a 100%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3);
} }
.spindle { .spindle {
width: 20px; width: 18px;
height: 20px; height: 18px;
border-radius: 50%; border-radius: 50%;
background: #000; background: #0a0a0a;
box-shadow: inset 0 2px 4px rgba(255, 255, 255, 0.1); box-shadow: inset 0 1px 3px rgba(255, 255, 255, 0.1);
} }
/* Reflection */ /* Light Reflection */
.reflection { .reflection {
position: absolute; position: absolute;
inset: 0; inset: 0;
@@ -212,164 +241,102 @@
background: linear-gradient( background: linear-gradient(
135deg, 135deg,
transparent 0%, transparent 0%,
rgba(255, 255, 255, 0.03) 30%, rgba(255, 255, 255, 0.04) 20%,
transparent 60% transparent 40%,
rgba(255, 255, 255, 0.02) 60%,
transparent 80%
); );
pointer-events: none; pointer-events: none;
} }
/* Album Cover - in front of disc */
.album-cover {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
height: 80%;
border-radius: 8px;
overflow: hidden;
box-shadow:
0 10px 40px rgba(0, 0, 0, 0.5),
0 20px 60px rgba(0, 0, 0, 0.3);
z-index: 10;
transition: transform 600ms cubic-bezier(0.23, 1, 0.32, 1);
}
/* When playing, slide cover to reveal disc */
.album-cover.revealed {
transform: translate(-70%, -50%);
}
.album-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Track Info */ /* Track Info */
.track-info { .track-info {
position: absolute; display: flex;
right: -60px; flex-direction: column;
top: 50%; align-items: center;
transform: translateY(-50%); text-align: center;
max-width: 300px; gap: 6px;
opacity: 0.5; max-width: 500px;
transition: opacity 300ms ease, transform 300ms ease;
}
.track-info.playing {
opacity: 1;
transform: translateY(-50%) translateX(-20px);
}
.vintage-badge {
display: inline-block;
padding: 6px 14px;
border-radius: 20px;
background: rgba(245, 158, 11, 0.15);
border: 1px solid rgba(245, 158, 11, 0.3);
margin-bottom: 16px;
}
.vintage-badge span {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.1em;
color: #fcd34d;
font-weight: 600;
} }
.track-title { .track-title {
font-family: 'Oswald', var(--font-sans), serif; font-size: clamp(20px, 3vw, 28px);
font-size: clamp(24px, 4vw, 36px); font-weight: 700;
font-weight: 600; color: var(--text-primary, white);
color: #fffbeb; margin: 0;
margin: 0 0 8px 0;
letter-spacing: 0.01em;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
} }
.track-artist { .track-artist {
font-family: 'Oswald', var(--font-sans), serif; font-size: clamp(14px, 2vw, 18px);
font-size: clamp(16px, 2.5vw, 20px); color: var(--alpha-70, rgba(255, 255, 255, 0.7));
color: rgba(254, 243, 199, 0.8); margin: 0;
margin: 0 0 4px 0;
} }
.track-album { .track-album {
font-family: 'Oswald', var(--font-sans), serif; font-size: clamp(12px, 1.5vw, 14px);
font-size: clamp(14px, 2vw, 16px); color: var(--alpha-50, rgba(255, 255, 255, 0.5));
color: rgba(254, 243, 199, 0.6); margin: 0;
font-style: italic; font-style: italic;
margin: 0 0 20px 0;
}
.vintage-details {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 20px;
font-family: monospace;
font-size: 12px;
}
.detail-row {
display: flex;
gap: 12px;
}
.detail-row .label {
color: rgba(252, 211, 77, 0.7);
min-width: 50px;
}
.detail-row .value {
color: rgba(254, 243, 199, 0.5);
} }
.quality-badge-wrapper { .quality-badge-wrapper {
margin-top: 16px; margin-top: 12px;
} }
/* Responsive */ /* Responsive */
@media (max-width: 1000px) { @media (max-width: 768px) {
.vinyl-content { .vinyl-panel {
flex-direction: column; padding: 70px 24px 130px;
height: auto;
gap: 20px; gap: 20px;
} }
.album-cover { .vinyl-container {
position: relative; width: min(350px, 50vw);
left: auto; height: min(350px, 50vw);
top: auto;
transform: none;
width: min(280px, 40vw);
height: min(280px, 40vw);
} }
.album-cover.playing { .album-cover.revealed {
transform: none; transform: translate(-60%, -50%);
}
.vinyl-disc-container {
display: none;
}
.track-info {
position: relative;
right: auto;
top: auto;
transform: none;
text-align: center;
opacity: 1;
}
.track-info.playing {
transform: none;
}
.vintage-details {
align-items: center;
}
.quality-badge-wrapper {
display: flex;
justify-content: center;
} }
} }
@media (max-height: 600px) { @media (max-height: 600px) {
.album-cover { .vinyl-container {
width: min(200px, 30vh); width: min(280px, 40vh);
height: min(200px, 30vh); height: min(280px, 40vh);
} }
.vinyl-disc-container { .track-info {
width: min(280px, 35vh); gap: 4px;
height: min(280px, 35vh);
}
.center-label {
width: 70px;
height: 70px;
}
.spindle {
width: 14px;
height: 14px;
} }
} }
</style> </style>