Compare commits

...

1 Commits

Author SHA1 Message Date
babdb61791 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>
2026-02-23 23:46:24 +03:00
3 changed files with 230 additions and 8 deletions

View File

@@ -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;
}

View File

@@ -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"/>

View File

@@ -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();