diff --git a/index.html b/index.html index 243af04..1b1cc0b 100644 --- a/index.html +++ b/index.html @@ -299,6 +299,27 @@ +
+ +
+
+
+
diff --git a/js/ui.js b/js/ui.js index f3eeeec..3dadc7a 100644 --- a/js/ui.js +++ b/js/ui.js @@ -5,6 +5,8 @@ import { SVG_DOWNLOAD, SVG_MENU, SVG_HEART, + SVG_VOLUME, + SVG_MUTE, formatTime, createPlaceholder, trackDataStore, @@ -900,6 +902,87 @@ export class UIRenderer { ''; } + // Fullscreen volume controls + const fsVolumeBtn = document.getElementById('fs-volume-btn'); + const fsVolumeBar = document.getElementById('fs-volume-bar'); + const fsVolumeFill = document.getElementById('fs-volume-fill'); + + if (fsVolumeBtn && fsVolumeBar && fsVolumeFill) { + const updateFsVolumeUI = () => { + const { muted } = audioPlayer; + const volume = this.player.userVolume; + fsVolumeBtn.innerHTML = muted || volume === 0 ? SVG_MUTE : SVG_VOLUME; + fsVolumeBtn.classList.toggle('muted', muted || volume === 0); + const effectiveVolume = muted ? 0 : volume * 100; + fsVolumeFill.style.setProperty('--fs-volume-level', `${effectiveVolume}%`); + fsVolumeFill.style.width = `${effectiveVolume}%`; + }; + + fsVolumeBtn.onclick = () => { + audioPlayer.muted = !audioPlayer.muted; + localStorage.setItem('muted', audioPlayer.muted); + updateFsVolumeUI(); + }; + + const setFsVolume = (e) => { + const rect = fsVolumeBar.getBoundingClientRect(); + const position = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); + const newVolume = position; + this.player.setVolume(newVolume); + if (audioPlayer.muted && newVolume > 0) { + audioPlayer.muted = false; + localStorage.setItem('muted', false); + } + updateFsVolumeUI(); + }; + + let isAdjustingFsVolume = false; + + fsVolumeBar.addEventListener('mousedown', (e) => { + isAdjustingFsVolume = true; + setFsVolume(e); + }); + + fsVolumeBar.addEventListener( + 'touchstart', + (e) => { + e.preventDefault(); + isAdjustingFsVolume = true; + const touch = e.touches[0]; + setFsVolume({ clientX: touch.clientX }); + }, + { passive: false } + ); + + document.addEventListener('mousemove', (e) => { + if (isAdjustingFsVolume) { + setFsVolume(e); + } + }); + + document.addEventListener( + 'touchmove', + (e) => { + if (isAdjustingFsVolume) { + const touch = e.touches[0]; + setFsVolume({ clientX: touch.clientX }); + } + }, + { passive: false } + ); + + document.addEventListener('mouseup', () => { + isAdjustingFsVolume = false; + }); + + document.addEventListener('touchend', () => { + isAdjustingFsVolume = false; + }); + + audioPlayer.addEventListener('volumechange', updateFsVolumeUI); + updateFsVolumeUI(); + } + const update = () => { if (document.getElementById('fullscreen-cover-overlay').style.display === 'none') return; diff --git a/styles.css b/styles.css index ab57028..bbbdd7c 100644 --- a/styles.css +++ b/styles.css @@ -2230,6 +2230,90 @@ input:checked + .slider::before { color: var(--primary); } +.fullscreen-volume-container { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; + margin-top: 1rem; +} + +.fs-volume-btn { + background: transparent; + border: none; + color: var(--foreground); + cursor: pointer; + padding: 0.5rem; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + width: 40px; + height: 40px; +} + +.fs-volume-btn:hover { + background: rgb(255, 255, 255, 0.1); + transform: scale(1.1); +} + +.fs-volume-btn.muted { + color: var(--muted-foreground); +} + +.fs-volume-bar { + width: 150px; + height: 6px; + background-color: rgb(255, 255, 255, 0.2); + border-radius: 3px; + cursor: pointer; + position: relative; + transition: height 0.2s ease; +} + +.fs-volume-bar:hover { + height: 8px; +} + +.fs-volume-fill { + height: 100%; + background-color: var(--foreground); + border-radius: 3px; + width: var(--fs-volume-level, 70%); + transition: width 0.1s ease; + position: relative; + pointer-events: none; +} + +.fs-volume-bar:hover .fs-volume-fill { + background-color: var(--highlight); +} + +.fs-volume-bar:hover .fs-volume-fill::after, +.fs-volume-bar:active .fs-volume-fill::after { + content: ''; + position: absolute; + right: -6px; + top: 50%; + transform: translateY(-50%); + width: 12px; + height: 12px; + background-color: var(--highlight); + border-radius: 50%; + box-shadow: 0 2px 4px rgb(0, 0, 0, 0.3); +} + +@media (max-width: 768px) { + .fullscreen-volume-container { + gap: 0.75rem; + } + + .fs-volume-bar { + width: 120px; + } +} + .fullscreen-actions { display: flex; gap: 1rem;