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;