ui(player): soften vinyl-stage halo, transparent-bg album placeholder, crossfade artwork swaps
Lint & Test / test (push) Successful in 1m50s
Lint & Test / test (push) Successful in 1m50s
- vinyl-stage background fades to transparent (matches fullscreen Listening Room) — no more rectangular dark card around the sleeve+disc composition - album-art placeholder SVG drops the opaque #282828 backdrop in favour of a translucent disc glyph so the sleeve cardstock shows through before the first artwork load - new swapArtworkSrc helper retriggers the .is-swapping keyframes so artwork changes crossfade instead of popping
This commit is contained in:
@@ -4432,6 +4432,12 @@ header .brand-sub {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
/* Kill the legacy albumArt3D rotateY/rotateX wobble inherited from
|
||||||
|
.album-art-container — the vinyl stage is a flat composition
|
||||||
|
and should not tilt. */
|
||||||
|
animation: none;
|
||||||
|
transform: none;
|
||||||
|
transform-style: flat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.album-art-container.vinyl-stage:hover { transform: none; }
|
.album-art-container.vinyl-stage:hover { transform: none; }
|
||||||
@@ -4612,14 +4618,27 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
|||||||
filter: blur(40px) saturate(1.8);
|
filter: blur(40px) saturate(1.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stage gets a warm ambient gradient (matches mockup) */
|
/* Honour reduced-motion: kill breathing pulse */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.vinyl-stage .sleeve #album-art {
|
||||||
|
animation: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stage halo: soft radial bloom that fades to transparent — same
|
||||||
|
treatment as the fullscreen player so the stage never reads as a
|
||||||
|
rectangular card pasted into the page. */
|
||||||
.album-art-container.vinyl-stage {
|
.album-art-container.vinyl-stage {
|
||||||
background:
|
background:
|
||||||
radial-gradient(ellipse at center, #1a1611 0%, var(--bg-deep) 80%);
|
radial-gradient(ellipse at center,
|
||||||
|
rgba(26, 22, 17, 0.85) 0%,
|
||||||
|
transparent 70%);
|
||||||
}
|
}
|
||||||
:root[data-theme="light"] .album-art-container.vinyl-stage {
|
:root[data-theme="light"] .album-art-container.vinyl-stage {
|
||||||
background:
|
background:
|
||||||
radial-gradient(ellipse at center, var(--bg-card-2) 0%, var(--bg-deep) 80%);
|
radial-gradient(ellipse at center,
|
||||||
|
rgba(255, 255, 255, 0.55) 0%,
|
||||||
|
transparent 75%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sleeve: cardstock card with album cover printed on its face.
|
/* Sleeve: cardstock card with album cover printed on its face.
|
||||||
@@ -4632,17 +4651,15 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
|||||||
width: 63%;
|
width: 63%;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
background: var(--bg-card-2);
|
background: transparent;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 4px 4px 24px rgba(0, 0, 0, 0.35),
|
|
||||||
-2px 8px 24px rgba(0, 0, 0, 0.5),
|
-2px 8px 24px rgba(0, 0, 0, 0.5),
|
||||||
-4px 18px 44px rgba(0, 0, 0, 0.35);
|
-4px 18px 44px rgba(0, 0, 0, 0.35);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
:root[data-theme="light"] .vinyl-stage .sleeve {
|
:root[data-theme="light"] .vinyl-stage .sleeve {
|
||||||
background: var(--bg-card);
|
background: transparent;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 4px 4px 18px rgba(0, 0, 0, 0.10),
|
|
||||||
-2px 8px 22px rgba(0, 0, 0, 0.20),
|
-2px 8px 22px rgba(0, 0, 0, 0.20),
|
||||||
-4px 18px 36px rgba(0, 0, 0, 0.12);
|
-4px 18px 36px rgba(0, 0, 0, 0.12);
|
||||||
}
|
}
|
||||||
@@ -4655,22 +4672,31 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
|||||||
size, blowing out the grid track. */
|
size, blowing out the grid track. */
|
||||||
.vinyl-stage .sleeve #album-art {
|
.vinyl-stage .sleeve #album-art {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5%;
|
top: 0;
|
||||||
left: 5%;
|
left: 0;
|
||||||
width: 90%;
|
width: 100%;
|
||||||
height: 90%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
box-shadow:
|
box-shadow: none;
|
||||||
inset 0 0 18px rgba(0, 0, 0, 0.35),
|
|
||||||
0 2px 6px rgba(0, 0, 0, 0.35);
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--bg-card);
|
background: transparent;
|
||||||
filter: contrast(0.96) saturate(0.92);
|
filter: contrast(0.96) saturate(0.92);
|
||||||
transition: filter 0.6s ease;
|
transition: filter 0.6s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Crossfade on artwork swap. Class toggled by player.js right before
|
||||||
|
src assignment so the new cover fades in instead of popping. */
|
||||||
|
.vinyl-stage .sleeve #album-art.is-swapping {
|
||||||
|
animation: sr-art-swap 650ms cubic-bezier(0.2, 0.7, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sr-art-swap {
|
||||||
|
0% { opacity: 0; }
|
||||||
|
100% { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Cardstock paper grain over the print — multiplies into the image */
|
/* Cardstock paper grain over the print — multiplies into the image */
|
||||||
.vinyl-stage .sleeve-grain {
|
.vinyl-stage .sleeve-grain {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -4683,17 +4709,11 @@ body.visualizer-active .vinyl-stage .spectrogram-canvas {
|
|||||||
}
|
}
|
||||||
:root[data-theme="light"] .vinyl-stage .sleeve-grain { opacity: 0.32; }
|
:root[data-theme="light"] .vinyl-stage .sleeve-grain { opacity: 0.32; }
|
||||||
|
|
||||||
/* Worn corner notch — tiny triangular wear on bottom-right */
|
/* Worn corner notch — hidden now that the cardstock sleeve is
|
||||||
|
transparent; the wedge would otherwise read as a stray dark
|
||||||
|
triangle clipped over the album art. */
|
||||||
.vinyl-stage .sleeve-corner {
|
.vinyl-stage .sleeve-corner {
|
||||||
position: absolute;
|
display: none;
|
||||||
width: 13%;
|
|
||||||
height: 13%;
|
|
||||||
bottom: -1px;
|
|
||||||
right: -1px;
|
|
||||||
background: var(--bg-deep);
|
|
||||||
clip-path: polygon(100% 0, 100% 100%, 0 100%);
|
|
||||||
opacity: 0.6;
|
|
||||||
z-index: 4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disc wrap — sits behind the sleeve, peeks out the right edge */
|
/* Disc wrap — sits behind the sleeve, peeks out the right edge */
|
||||||
|
|||||||
@@ -187,7 +187,7 @@
|
|||||||
<div class="vinyl-stage album-art-container">
|
<div class="vinyl-stage album-art-container">
|
||||||
<img id="album-art-glow" class="album-art-glow" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E" alt="" aria-hidden="true">
|
<img id="album-art-glow" class="album-art-glow" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E" alt="" aria-hidden="true">
|
||||||
<div class="sleeve">
|
<div class="sleeve">
|
||||||
<img id="album-art" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3Cpath fill='%236a6a6a' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E" alt="Album Art">
|
<img id="album-art" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Cpath fill='%236a6a6a' opacity='0.35' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E" alt="Album Art">
|
||||||
<div class="sleeve-grain" aria-hidden="true"></div>
|
<div class="sleeve-grain" aria-hidden="true"></div>
|
||||||
<div class="sleeve-corner" aria-hidden="true"></div>
|
<div class="sleeve-corner" aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -613,6 +613,18 @@ export function setupProgressDrag(bar, fill) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace the album-art src and replay the .is-swapping CSS animation
|
||||||
|
// so the new artwork crossfades in instead of popping. Re-toggling the
|
||||||
|
// class across rAF restarts the keyframes even if it was already on.
|
||||||
|
function swapArtworkSrc(imgEl, newSrc) {
|
||||||
|
if (!imgEl) return;
|
||||||
|
if (imgEl.src === newSrc) return;
|
||||||
|
imgEl.classList.remove('is-swapping');
|
||||||
|
void imgEl.offsetWidth;
|
||||||
|
imgEl.src = newSrc;
|
||||||
|
imgEl.classList.add('is-swapping');
|
||||||
|
}
|
||||||
|
|
||||||
export function updateUI(status) {
|
export function updateUI(status) {
|
||||||
setLastStatus(status);
|
setLastStatus(status);
|
||||||
|
|
||||||
@@ -639,7 +651,7 @@ export function updateUI(status) {
|
|||||||
|
|
||||||
if (artworkKey !== lastArtworkKey) {
|
if (artworkKey !== lastArtworkKey) {
|
||||||
lastArtworkKey = artworkKey;
|
lastArtworkKey = artworkKey;
|
||||||
const placeholderArt = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3Cpath fill='%236a6a6a' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E";
|
const placeholderArt = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Cpath fill='%236a6a6a' opacity='0.35' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E";
|
||||||
const placeholderGlow = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E";
|
const placeholderGlow = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E";
|
||||||
if (artworkSource) {
|
if (artworkSource) {
|
||||||
fetch(`/api/media/artwork?_=${Date.now()}`, {
|
fetch(`/api/media/artwork?_=${Date.now()}`, {
|
||||||
@@ -651,7 +663,7 @@ export function updateUI(status) {
|
|||||||
const oldBlobUrl = currentArtworkBlobUrl;
|
const oldBlobUrl = currentArtworkBlobUrl;
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
currentArtworkBlobUrl = url;
|
currentArtworkBlobUrl = url;
|
||||||
dom.albumArt.src = url;
|
swapArtworkSrc(dom.albumArt, url);
|
||||||
dom.miniAlbumArt.src = url;
|
dom.miniAlbumArt.src = url;
|
||||||
if (dom.albumArtGlow) dom.albumArtGlow.src = url;
|
if (dom.albumArtGlow) dom.albumArtGlow.src = url;
|
||||||
if (oldBlobUrl) setTimeout(() => URL.revokeObjectURL(oldBlobUrl), 1000);
|
if (oldBlobUrl) setTimeout(() => URL.revokeObjectURL(oldBlobUrl), 1000);
|
||||||
@@ -662,7 +674,7 @@ export function updateUI(status) {
|
|||||||
URL.revokeObjectURL(currentArtworkBlobUrl);
|
URL.revokeObjectURL(currentArtworkBlobUrl);
|
||||||
currentArtworkBlobUrl = null;
|
currentArtworkBlobUrl = null;
|
||||||
}
|
}
|
||||||
dom.albumArt.src = placeholderArt;
|
swapArtworkSrc(dom.albumArt, placeholderArt);
|
||||||
dom.miniAlbumArt.src = placeholderArt;
|
dom.miniAlbumArt.src = placeholderArt;
|
||||||
if (dom.albumArtGlow) dom.albumArtGlow.src = placeholderGlow;
|
if (dom.albumArtGlow) dom.albumArtGlow.src = placeholderGlow;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user