Update media-server: Vinyl record mode and accent color picker
- Vinyl mode: album art as center label with grooves, spindle hole, vignette - Smooth transition between normal and vinyl modes - Accent color picker with 9 preset colors in header - Fix progress bar hover layout shift (use scaleY instead of height) - Fix glow position jump when toggling vinyl mode Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -170,6 +170,73 @@
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* Accent Color Picker */
|
||||
.accent-picker {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.accent-picker-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.accent-picker-btn:hover {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.accent-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
border: 2px solid var(--border);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.accent-picker-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 4px);
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
gap: 6px;
|
||||
z-index: 100;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
display: none;
|
||||
grid-template-columns: repeat(3, 24px);
|
||||
}
|
||||
|
||||
.accent-picker-dropdown.open {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.accent-swatch {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.accent-swatch:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.accent-swatch.active {
|
||||
border-color: var(--text-primary);
|
||||
}
|
||||
|
||||
#locale-select {
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border);
|
||||
@@ -308,10 +375,12 @@
|
||||
border-radius: 8px;
|
||||
filter: blur(40px) saturate(1.5);
|
||||
opacity: 0.5;
|
||||
transform: scale(1.1);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) scale(1.1);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.5s ease;
|
||||
transition: opacity 0.5s ease, border-radius 0.6s ease;
|
||||
}
|
||||
|
||||
#album-art {
|
||||
@@ -323,6 +392,8 @@
|
||||
background: var(--bg-tertiary);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
transition: border-radius 0.6s ease, width 0.6s ease, height 0.6s ease, box-shadow 0.6s ease, margin 0.6s ease, filter 0.6s ease;
|
||||
}
|
||||
|
||||
:root[data-theme="light"] .album-art-glow {
|
||||
@@ -330,17 +401,84 @@
|
||||
filter: blur(50px) saturate(1.8);
|
||||
}
|
||||
|
||||
/* Vinyl Spin Animation */
|
||||
/* Vinyl Record Mode */
|
||||
.album-art-container.vinyl #album-art {
|
||||
border-radius: 50%;
|
||||
transition: border-radius 0.5s ease, box-shadow 0.5s ease;
|
||||
box-shadow: 0 0 0 8px var(--bg-tertiary), 0 0 0 10px var(--border), 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
margin: 45px;
|
||||
filter: saturate(0.8) brightness(0.92) contrast(1.05);
|
||||
box-shadow:
|
||||
0 0 0 3px #2a2a2a,
|
||||
0 0 0 5px #1a1a1a,
|
||||
0 0 0 6px rgba(255,255,255,0.05),
|
||||
0 0 0 12px #1a1a1a,
|
||||
0 0 0 13px rgba(255,255,255,0.03),
|
||||
0 0 0 20px #1a1a1a,
|
||||
0 0 0 21px rgba(255,255,255,0.05),
|
||||
0 0 0 28px #1a1a1a,
|
||||
0 0 0 29px rgba(255,255,255,0.03),
|
||||
0 0 0 36px #1a1a1a,
|
||||
0 0 0 37px rgba(255,255,255,0.05),
|
||||
0 0 0 42px #1a1a1a,
|
||||
0 0 0 43px #2a2a2a,
|
||||
0 0 0 45px #111,
|
||||
0 4px 15px 45px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
/* Vinyl label vignette overlay */
|
||||
.album-art-container.vinyl::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 210px;
|
||||
height: 210px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
transparent 50%,
|
||||
rgba(0,0,0,0.25) 100%
|
||||
);
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.6s ease;
|
||||
}
|
||||
|
||||
.album-art-container.vinyl.spinning::before,
|
||||
.album-art-container.vinyl.paused::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.album-art-container.vinyl .album-art-glow {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Center spindle hole */
|
||||
.album-art-container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: #0a0a0a;
|
||||
border: 2px solid #444;
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.8);
|
||||
z-index: 3;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease 0.3s;
|
||||
}
|
||||
|
||||
.album-art-container.vinyl::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.album-art-container.vinyl.spinning #album-art {
|
||||
animation: vinylSpin 12s linear infinite;
|
||||
}
|
||||
@@ -412,11 +550,11 @@
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: height 0.15s ease;
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.progress-bar:hover {
|
||||
height: 8px;
|
||||
transform: scaleY(1.4);
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
@@ -1429,6 +1567,27 @@
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
.album-art-container.vinyl #album-art {
|
||||
width: 170px;
|
||||
height: 170px;
|
||||
margin: 40px;
|
||||
box-shadow:
|
||||
0 0 0 3px #2a2a2a,
|
||||
0 0 0 5px #1a1a1a,
|
||||
0 0 0 6px rgba(255,255,255,0.05),
|
||||
0 0 0 12px #1a1a1a,
|
||||
0 0 0 13px rgba(255,255,255,0.03),
|
||||
0 0 0 20px #1a1a1a,
|
||||
0 0 0 21px rgba(255,255,255,0.05),
|
||||
0 0 0 28px #1a1a1a,
|
||||
0 0 0 29px rgba(255,255,255,0.03),
|
||||
0 0 0 36px #1a1a1a,
|
||||
0 0 0 37px rgba(255,255,255,0.04),
|
||||
0 0 0 38px #2a2a2a,
|
||||
0 0 0 40px #111,
|
||||
0 4px 12px 40px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
#track-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
@@ -66,6 +66,12 @@
|
||||
<span class="version-label" id="version-label"></span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<div class="accent-picker">
|
||||
<button class="accent-picker-btn" onclick="toggleAccentPicker()" title="Accent color">
|
||||
<span class="accent-dot" id="accentDot"></span>
|
||||
</button>
|
||||
<div class="accent-picker-dropdown" id="accentDropdown"></div>
|
||||
</div>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" data-i18n-title="player.theme" title="Toggle theme" id="theme-toggle">
|
||||
<svg id="theme-icon-sun" viewBox="0 0 24 24" style="display: none;">
|
||||
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/>
|
||||
|
||||
@@ -156,6 +156,62 @@
|
||||
setTheme(newTheme);
|
||||
}
|
||||
|
||||
// Accent color management
|
||||
const accentPresets = [
|
||||
{ name: 'Green', color: '#1db954', hover: '#1ed760' },
|
||||
{ name: 'Blue', color: '#3b82f6', hover: '#60a5fa' },
|
||||
{ name: 'Purple', color: '#8b5cf6', hover: '#a78bfa' },
|
||||
{ name: 'Pink', color: '#ec4899', hover: '#f472b6' },
|
||||
{ name: 'Orange', color: '#f97316', hover: '#fb923c' },
|
||||
{ name: 'Red', color: '#ef4444', hover: '#f87171' },
|
||||
{ name: 'Teal', color: '#14b8a6', hover: '#2dd4bf' },
|
||||
{ name: 'Cyan', color: '#06b6d4', hover: '#22d3ee' },
|
||||
{ name: 'Yellow', color: '#eab308', hover: '#facc15' },
|
||||
];
|
||||
|
||||
function initAccentColor() {
|
||||
const saved = localStorage.getItem('accentColor');
|
||||
if (saved) {
|
||||
const preset = accentPresets.find(p => p.color === saved);
|
||||
if (preset) applyAccentColor(preset.color, preset.hover);
|
||||
}
|
||||
renderAccentSwatches();
|
||||
}
|
||||
|
||||
function applyAccentColor(color, hover) {
|
||||
document.documentElement.style.setProperty('--accent', color);
|
||||
document.documentElement.style.setProperty('--accent-hover', hover);
|
||||
localStorage.setItem('accentColor', color);
|
||||
}
|
||||
|
||||
function renderAccentSwatches() {
|
||||
const dropdown = document.getElementById('accentDropdown');
|
||||
if (!dropdown) return;
|
||||
const current = localStorage.getItem('accentColor') || '#1db954';
|
||||
dropdown.innerHTML = accentPresets.map(p =>
|
||||
`<div class="accent-swatch ${p.color === current ? 'active' : ''}"
|
||||
style="background: ${p.color}"
|
||||
onclick="selectAccentColor('${p.color}', '${p.hover}')"
|
||||
title="${p.name}"></div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function selectAccentColor(color, hover) {
|
||||
applyAccentColor(color, hover);
|
||||
renderAccentSwatches();
|
||||
document.getElementById('accentDropdown').classList.remove('open');
|
||||
}
|
||||
|
||||
function toggleAccentPicker() {
|
||||
document.getElementById('accentDropdown').classList.toggle('open');
|
||||
}
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!e.target.closest('.accent-picker')) {
|
||||
document.getElementById('accentDropdown')?.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
// Vinyl mode
|
||||
let vinylMode = localStorage.getItem('vinylMode') === 'true';
|
||||
let currentPlayState = 'idle';
|
||||
@@ -380,8 +436,9 @@
|
||||
// Cache DOM references
|
||||
cacheDom();
|
||||
|
||||
// Initialize theme
|
||||
// Initialize theme and accent color
|
||||
initTheme();
|
||||
initAccentColor();
|
||||
|
||||
// Initialize vinyl mode
|
||||
applyVinylMode();
|
||||
|
||||
Reference in New Issue
Block a user