+
+

+
-
-
-
-
-
-
-
-
-
-
-
- Up Next:
-
-
-
-
-
-
0:00
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Up Next
+
+
-
0:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/icons.ts b/js/icons.ts
index 39ebf20..a2ed6ec 100644
--- a/js/icons.ts
+++ b/js/icons.ts
@@ -11,6 +11,8 @@ export { default as SVG_CLOCK } from '!lucide/clock.svg?svg&icon';
export { default as SVG_CLOSE } from '!lucide/x.svg?svg&icon';
export { default as SVG_DISC } from '!lucide/disc.svg?svg&icon';
export { default as SVG_DOWNLOAD } from '!lucide/download.svg?svg&icon';
+export { default as SVG_EYE } from '!lucide/eye.svg?svg&icon';
+export { default as SVG_EYE_OFF } from '!lucide/eye-off.svg?svg&icon';
export { default as SVG_EQUAL } from '!lucide/equal.svg?svg&icon';
export { default as SVG_FACEBOOK } from '../images/facebook.svg?svg&icon';
export { default as SVG_FOLDER_PLUS } from '!lucide/folder-plus.svg?svg&icon';
diff --git a/js/lyrics.js b/js/lyrics.js
index 484c5c1..3e33116 100644
--- a/js/lyrics.js
+++ b/js/lyrics.js
@@ -964,6 +964,74 @@ themeObserver.observe(document.documentElement, {
attributeFilter: ['data-theme', 'style'],
});
+function applyFullscreenLyricsShadowTweaks(amLyrics, container) {
+ if (!amLyrics || container?.id !== 'fullscreen-lyrics-content') return;
+
+ const injectStyle = () => {
+ const root = amLyrics.shadowRoot;
+ if (!root) return false;
+
+ let styleEl = root.getElementById('monochrome-fullscreen-lyrics-tweaks');
+ if (!styleEl) {
+ styleEl = document.createElement('style');
+ styleEl.id = 'monochrome-fullscreen-lyrics-tweaks';
+ root.appendChild(styleEl);
+ }
+
+ styleEl.textContent = `
+ .lyrics-container {
+ scrollbar-width: none !important;
+ -ms-overflow-style: none !important;
+ }
+
+ .lyrics-container::-webkit-scrollbar {
+ width: 0 !important;
+ height: 0 !important;
+ display: none !important;
+ background: transparent !important;
+ }
+
+ .lyrics-line {
+ transition:
+ opacity 0.42s ease,
+ transform 0.55s cubic-bezier(0.22, 1, 0.36, 1) var(--lyrics-line-delay, 0ms),
+ filter 0.48s cubic-bezier(0.22, 1, 0.36, 1) !important;
+ }
+
+ .lyrics-line-container {
+ transition:
+ transform 0.72s cubic-bezier(0.22, 1, 0.36, 1),
+ background-color 0.3s ease,
+ color 0.3s ease !important;
+ }
+
+ .lyrics-line.active .lyrics-line-container,
+ .lyrics-line.pre-active .lyrics-line-container {
+ transition:
+ transform 0.56s cubic-bezier(0.22, 1, 0.36, 1),
+ background-color 0.22s ease,
+ color 0.22s ease !important;
+ }
+ `;
+
+ return true;
+ };
+
+ if (injectStyle()) return;
+
+ let attempts = 0;
+ const maxAttempts = 24;
+ const tryInject = () => {
+ if (injectStyle()) return;
+ attempts += 1;
+ if (attempts < maxAttempts) {
+ requestAnimationFrame(tryInject);
+ }
+ };
+
+ requestAnimationFrame(tryInject);
+}
+
async function renderLyricsComponent(container, track, audioPlayer, lyricsManager) {
container.innerHTML = '
Loading lyrics...
';
@@ -1006,6 +1074,7 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage
amLyrics.style.width = '100%';
container.appendChild(amLyrics);
+ applyFullscreenLyricsShadowTweaks(amLyrics, container);
lyricsManager.setupLyricsObserver(amLyrics);
diff --git a/js/ui.js b/js/ui.js
index a6781e2..9350795 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -15,7 +15,7 @@ import {
escapeHtml,
getShareUrl,
} from './utils.js';
-import { openLyricsPanel } from './lyrics.js';
+import { openLyricsPanel, renderLyricsInFullscreen, clearFullscreenLyricsSync } from './lyrics.js';
import {
recentActivityManager,
backgroundSettings,
@@ -27,9 +27,6 @@ import {
contentBlockingSettings,
settingsUiState,
fullscreenCoverNoRoundSettings,
- fullscreenCoverVanillaTiltSettings,
- fullscreenCoverTiltDistanceSettings,
- fullscreenCoverTiltSpeedSettings,
} from './storage.js';
import { db } from './db.js';
import { getVibrantColorFromImage } from './vibrant-color.js';
@@ -61,6 +58,8 @@ import {
SVG_HEART,
SVG_VOLUME,
SVG_MUTE,
+ SVG_EYE,
+ SVG_EYE_OFF,
SVG_HEART_FILLED,
SVG_CLOSE,
SVG_SORT,
@@ -89,6 +88,11 @@ import {
SVG_CHECKBOX,
} from './icons.js';
+const setFullscreenUIToggleIcon = (button, visualizerOnlyMode) => {
+ if (!button) return;
+ button.innerHTML = visualizerOnlyMode ? SVG_EYE(24) : SVG_EYE_OFF(24);
+};
+
function sortTracks(tracks, sortType) {
if (sortType === 'custom') return [...tracks];
const sorted = [...tracks];
@@ -151,6 +155,8 @@ export class UIRenderer {
this.renderLock = false;
this.lastRecommendedTracks = [];
this.currentArtistId = null;
+ this.fullscreenLyricsVisible = true;
+ this.fullscreenPlaybackStateCleanup = null;
// Listen for dynamic color reset events
window.addEventListener('reset-dynamic-color', () => {
@@ -177,20 +183,8 @@ export class UIRenderer {
} else {
overlay.classList.remove('fullscreen-cover-no-round');
}
- if (coverImage) {
- if (fullscreenCoverVanillaTiltSettings.isEnabled() && window.VanillaTilt) {
- if (coverImage.vanillaTilt) {
- coverImage.vanillaTilt.destroy();
- }
- window.VanillaTilt.init(coverImage, {
- max: fullscreenCoverTiltDistanceSettings.getValue(),
- speed: fullscreenCoverTiltSpeedSettings.getValue(),
- glare: true,
- 'max-glare': 0.3,
- });
- } else if (coverImage.vanillaTilt) {
- coverImage.vanillaTilt.destroy();
- }
+ if (coverImage?.vanillaTilt) {
+ coverImage.vanillaTilt.destroy();
}
}
});
@@ -1167,6 +1161,23 @@ export class UIRenderer {
root.style.removeProperty('--track-hover-bg');
}
+ getFullscreenQualityBadgeHTML(track) {
+ const nowPlayingTitle = document.querySelector('.now-playing-bar .title');
+ if (nowPlayingTitle && this.player?.currentTrack?.id === track?.id) {
+ const badges = Array.from(nowPlayingTitle.querySelectorAll('.shaka-quality-badge, .quality-badge'));
+ const liveBadge = badges.find((badge) => getComputedStyle(badge).display !== 'none') || badges[0];
+ if (liveBadge) {
+ const badgeClone = liveBadge.cloneNode(true);
+ if (badgeClone instanceof HTMLElement) {
+ badgeClone.style.removeProperty('display');
+ }
+ return badgeClone.outerHTML;
+ }
+ }
+
+ return createQualityBadgeHTML(track);
+ }
+
async updateFullscreenMetadata(track, nextTrack) {
if (!track) return;
const overlay = document.getElementById('fullscreen-cover-overlay');
@@ -1264,7 +1275,7 @@ export class UIRenderer {
await this.extractAndApplyColor(this.api.getCoverUrl(track.album?.cover, '80'));
}
- const qualityBadge = createQualityBadgeHTML(track);
+ const qualityBadge = this.getFullscreenQualityBadgeHTML(track);
title.innerHTML = `${escapeHtml(track.title)} ${qualityBadge}`;
artist.textContent = getTrackArtists(track);
@@ -1278,11 +1289,14 @@ export class UIRenderer {
async showFullscreenCover(track, nextTrack, lyricsManager, activeElement) {
if (!track) return;
+ this.fullscreenVisualizerSuppressed = true;
if (window.location.hash !== '#fullscreen') {
window.history.pushState({ fullscreen: true }, '', '#fullscreen');
}
const overlay = document.getElementById('fullscreen-cover-overlay');
const nextTrackEl = document.getElementById('fullscreen-next-track');
+ const lyricsPane = document.getElementById('fullscreen-lyrics-pane');
+ const lyricsContent = document.getElementById('fullscreen-lyrics-content');
const lyricsToggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn');
await this.updateFullscreenMetadata(track, nextTrack);
@@ -1295,27 +1309,33 @@ export class UIRenderer {
nextTrackEl.classList.remove('animate-in');
}
- if (lyricsManager && activeElement) {
- lyricsToggleBtn.style.display = 'flex';
- lyricsToggleBtn.classList.remove('active');
-
- const toggleLyrics = () => {
- openLyricsPanel(track, activeElement, lyricsManager);
- lyricsToggleBtn.classList.toggle('active');
- };
-
- const newToggleBtn = lyricsToggleBtn.cloneNode(true);
- lyricsToggleBtn.parentNode.replaceChild(newToggleBtn, lyricsToggleBtn);
- newToggleBtn.addEventListener('click', toggleLyrics);
+ const canRenderLyrics = Boolean(lyricsManager && activeElement && lyricsPane && lyricsContent && track.type !== 'video');
+ if (canRenderLyrics) {
+ lyricsToggleBtn.style.display = 'none';
+ overlay.classList.remove('lyrics-unavailable');
+ clearFullscreenLyricsSync(lyricsContent);
+ await renderLyricsInFullscreen(track, activeElement, lyricsManager, lyricsContent);
} else {
lyricsToggleBtn.style.display = 'none';
+ overlay.classList.add('lyrics-unavailable');
+ if (lyricsContent) {
+ clearFullscreenLyricsSync(lyricsContent);
+ lyricsContent.innerHTML = '
Lyrics are not available for this track.
';
+ }
}
const playerBar = document.querySelector('.now-playing-bar');
if (playerBar) playerBar.style.display = 'none';
+ if (sidePanelManager.isActive('lyrics') || sidePanelManager.isActive('queue')) {
+ sidePanelManager.close();
+ }
+ const mainContent = document.querySelector('.main-content');
+ if (mainContent instanceof HTMLElement) {
+ this.fullscreenMainContentOverflow = mainContent.style.overflowY;
+ mainContent.style.overflowY = 'hidden';
+ }
this.setupFullscreenControls();
-
overlay.style.display = 'flex';
if (fullscreenCoverNoRoundSettings.isEnabled()) {
@@ -1325,75 +1345,41 @@ export class UIRenderer {
}
const coverImage = document.getElementById('fullscreen-cover-image');
- if (fullscreenCoverVanillaTiltSettings.isEnabled() && coverImage && window.VanillaTilt) {
- window.VanillaTilt.init(coverImage, {
- max: fullscreenCoverTiltDistanceSettings.getValue(),
- speed: fullscreenCoverTiltSpeedSettings.getValue(),
- glare: true,
- 'max-glare': 0.3,
- });
+ if (coverImage?.vanillaTilt) {
+ coverImage.vanillaTilt.destroy();
}
- const startVisualizer = async () => {
- if (!visualizerSettings.isEnabled()) {
- if (this.visualizer) this.visualizer.stop();
- return;
- }
-
- if (!this.visualizer && activeElement) {
- const canvas = document.getElementById('visualizer-canvas');
- if (canvas) {
- this.visualizer = new Visualizer(canvas, activeElement);
- await this.visualizer.initPresets();
- }
- }
- if (this.visualizer) {
- await this.visualizer.start();
- }
-
- // Add visualizer-active class for enhanced drop shadow
- overlay.classList.add('visualizer-active');
- };
-
// Setup UI toggle button
this.setupUIToggleButton(overlay);
-
- if (localStorage.getItem('epilepsy-warning-dismissed') === 'true') {
- await startVisualizer();
- } else {
- const modal = document.getElementById('epilepsy-warning-modal');
- if (modal) {
- modal.classList.add('active');
-
- const acceptBtn = document.getElementById('epilepsy-accept-btn');
- const cancelBtn = document.getElementById('epilepsy-cancel-btn');
-
- acceptBtn.onclick = async () => {
- modal.classList.remove('active');
- localStorage.setItem('epilepsy-warning-dismissed', 'true');
- await startVisualizer();
- };
- cancelBtn.onclick = () => {
- modal.classList.remove('active');
- this.closeFullscreenCover();
- };
- } else {
- await startVisualizer();
- }
- }
+ this.setupControlsAutoHide(overlay);
+ await this.refreshFullscreenVisualizerState(activeElement);
}
closeFullscreenCover() {
const overlay = document.getElementById('fullscreen-cover-overlay');
const coverImage = document.getElementById('fullscreen-cover-image');
+ const lyricsContent = document.getElementById('fullscreen-lyrics-content');
if (coverImage && coverImage.vanillaTilt) {
coverImage.vanillaTilt.destroy();
}
+ if (lyricsContent) {
+ clearFullscreenLyricsSync(lyricsContent);
+ lyricsContent.innerHTML = '
Lyrics appear here.
';
+ }
overlay.style.display = 'none';
- overlay.classList.remove('visualizer-active', 'ui-hidden', 'fullscreen-cover-no-round');
+ overlay.classList.remove('visualizer-active', 'ui-hidden', 'fullscreen-cover-no-round', 'fullscreen-paused');
const playerBar = document.querySelector('.now-playing-bar');
if (playerBar) playerBar.style.removeProperty('display');
+ const mainContent = document.querySelector('.main-content');
+ if (mainContent instanceof HTMLElement) {
+ if (typeof this.fullscreenMainContentOverflow === 'string' && this.fullscreenMainContentOverflow.length > 0) {
+ mainContent.style.overflowY = this.fullscreenMainContentOverflow;
+ } else {
+ mainContent.style.removeProperty('overflow-y');
+ }
+ this.fullscreenMainContentOverflow = null;
+ }
if (this.player?.currentTrack?.type === 'video') {
const coverContainer = document.querySelector('.now-playing-bar .track-info');
@@ -1425,6 +1411,7 @@ export class UIRenderer {
if (this.visualizer) {
this.visualizer.stop();
}
+ this.fullscreenVisualizerSuppressed = false;
// Clear UI toggle button timers
if (this.uiToggleMouseTimer) {
@@ -1433,13 +1420,115 @@ export class UIRenderer {
}
}
+ async startFullscreenVisualizer(activeElement, overlay) {
+ if (!activeElement) return;
+
+ if (!this.visualizer) {
+ const canvas = document.getElementById('visualizer-canvas');
+ if (canvas) {
+ this.visualizer = new Visualizer(canvas, activeElement);
+ await this.visualizer.initPresets();
+ }
+ }
+
+ if (this.visualizer) {
+ await this.visualizer.start();
+ overlay.classList.add('visualizer-active');
+ }
+ }
+
+ async ensureVisualizerPermission(activeElement, overlay, { closeOnCancel = false } = {}) {
+ if (localStorage.getItem('epilepsy-warning-dismissed') === 'true') {
+ await this.startFullscreenVisualizer(activeElement, overlay);
+ return true;
+ }
+
+ const modal = document.getElementById('epilepsy-warning-modal');
+ if (!modal) {
+ await this.startFullscreenVisualizer(activeElement, overlay);
+ return true;
+ }
+
+ return await new Promise((resolve) => {
+ modal.classList.add('active');
+
+ const acceptBtn = document.getElementById('epilepsy-accept-btn');
+ const cancelBtn = document.getElementById('epilepsy-cancel-btn');
+
+ acceptBtn.onclick = async () => {
+ modal.classList.remove('active');
+ localStorage.setItem('epilepsy-warning-dismissed', 'true');
+ await this.startFullscreenVisualizer(activeElement, overlay);
+ resolve(true);
+ };
+
+ cancelBtn.onclick = () => {
+ modal.classList.remove('active');
+ if (closeOnCancel) {
+ this.closeFullscreenCover();
+ }
+ resolve(false);
+ };
+ });
+ }
+
+ async refreshFullscreenVisualizerState(activeElement, { closeOnCancel = false } = {}) {
+ const overlay = document.getElementById('fullscreen-cover-overlay');
+ const visualizerBtn = document.getElementById('fs-visualizer-btn');
+ const toggleBtn = document.getElementById('toggle-ui-btn');
+ const isVideoTrack = this.player?.currentTrack?.type === 'video';
+ const enabled = visualizerSettings.isEnabled() && !isVideoTrack && !this.fullscreenVisualizerSuppressed;
+
+ if (!overlay) return;
+
+ if (visualizerBtn) {
+ visualizerBtn.style.display = isVideoTrack ? 'none' : 'flex';
+ visualizerBtn.classList.toggle('active', enabled);
+ visualizerBtn.title = enabled ? 'Disable Visualizer' : 'Use Visualizer';
+ }
+
+ if (!enabled) {
+ overlay.classList.remove('visualizer-active');
+ overlay.classList.remove('ui-hidden');
+ if (this.visualizer) {
+ this.visualizer.stop();
+ }
+ if (toggleBtn) {
+ toggleBtn.classList.remove('active', 'visible');
+ toggleBtn.title = 'Hide UI';
+ setFullscreenUIToggleIcon(toggleBtn, false);
+ }
+ return;
+ }
+
+ const allowed = await this.ensureVisualizerPermission(activeElement, overlay, { closeOnCancel });
+ if (!allowed) {
+ this.fullscreenVisualizerSuppressed = true;
+ overlay.classList.remove('visualizer-active');
+ if (this.visualizer) {
+ this.visualizer.stop();
+ }
+ if (visualizerBtn) {
+ visualizerBtn.classList.remove('active');
+ visualizerBtn.title = 'Use Visualizer';
+ }
+ }
+ }
+
setupUIToggleButton(overlay) {
const toggleBtn = document.getElementById('toggle-ui-btn');
if (!toggleBtn) return;
+ const updateToggleButtonIcon = () => {
+ const visualizerOnlyMode =
+ overlay.classList.contains('ui-hidden') && overlay.classList.contains('visualizer-active');
+ setFullscreenUIToggleIcon(toggleBtn, visualizerOnlyMode);
+ };
+
let isUIHidden = overlay.classList.contains('ui-hidden');
toggleBtn.classList.toggle('active', isUIHidden);
toggleBtn.title = isUIHidden ? 'Show UI' : 'Hide UI';
+ updateToggleButtonIcon();
// Show button
const showButton = () => {
@@ -1458,12 +1547,39 @@ export class UIRenderer {
showButton();
}
- const toggleUI = (e) => {
+ const toggleUI = async (e) => {
if (e) e.stopPropagation();
+ if (!overlay.classList.contains('visualizer-active')) {
+ const isVideoTrack = this.player?.currentTrack?.type === 'video';
+ if (isVideoTrack) {
+ overlay.classList.remove('ui-hidden');
+ isUIHidden = false;
+ toggleBtn.classList.remove('active');
+ toggleBtn.title = 'Hide UI';
+ updateToggleButtonIcon();
+ showButton();
+ return;
+ }
+
+ this.fullscreenVisualizerSuppressed = false;
+ visualizerSettings.setEnabled(true);
+ await this.refreshFullscreenVisualizerState(this.player?.activeElement);
+
+ if (!overlay.classList.contains('visualizer-active')) {
+ overlay.classList.remove('ui-hidden');
+ isUIHidden = false;
+ toggleBtn.classList.remove('active');
+ toggleBtn.title = 'Hide UI';
+ updateToggleButtonIcon();
+ showButton();
+ return;
+ }
+ }
isUIHidden = !isUIHidden;
overlay.classList.toggle('ui-hidden', isUIHidden);
toggleBtn.classList.toggle('active', isUIHidden);
toggleBtn.title = isUIHidden ? 'Show UI' : 'Hide UI';
+ updateToggleButtonIcon();
if (isUIHidden) {
hideButton();
@@ -1508,12 +1624,21 @@ export class UIRenderer {
};
}
+ setupControlsAutoHide(overlay) {
+ if (this.controlsIdleCleanup) this.controlsIdleCleanup();
+ overlay.classList.remove('controls-idle');
+
+ this.controlsIdleCleanup = () => {
+ overlay.classList.remove('controls-idle');
+ };
+ }
setupFullscreenControls() {
const playBtn = document.getElementById('fs-play-pause-btn');
const prevBtn = document.getElementById('fs-prev-btn');
const nextBtn = document.getElementById('fs-next-btn');
const shuffleBtn = document.getElementById('fs-shuffle-btn');
const repeatBtn = document.getElementById('fs-repeat-btn');
+ const visualizerBtn = document.getElementById('fs-visualizer-btn');
const progressBar = document.getElementById('fs-progress-bar');
const progressFill = document.getElementById('fs-progress-fill');
const currentTimeEl = document.getElementById('fs-current-time');
@@ -1574,6 +1699,22 @@ export class UIRenderer {
}
};
+ if (visualizerBtn) {
+ visualizerBtn.onclick = async () => {
+ if (this.fullscreenVisualizerSuppressed) {
+ this.fullscreenVisualizerSuppressed = false;
+ visualizerSettings.setEnabled(true);
+ } else if (visualizerSettings.isEnabled()) {
+ visualizerSettings.setEnabled(false);
+ this.fullscreenVisualizerSuppressed = false;
+ } else {
+ this.fullscreenVisualizerSuppressed = false;
+ visualizerSettings.setEnabled(true);
+ }
+ await this.refreshFullscreenVisualizerState(this.player.activeElement);
+ };
+ }
+
// Progress bar with drag support
let isFsSeeking = false;
let wasFsPlaying = false;
diff --git a/styles.css b/styles.css
index c8cb658..16d7d69 100644
--- a/styles.css
+++ b/styles.css
@@ -970,6 +970,7 @@ ul {
display: grid;
height: 100vh;
height: 100dvh;
+ min-height: 0;
grid-template:
'sidebar main' 1fr
'player player' auto / 210px 1fr;
@@ -977,6 +978,7 @@ ul {
.sidebar {
grid-area: sidebar;
+ min-height: 0;
background-color: var(--background);
border-right: 1px solid var(--border);
padding: 1.25rem;
@@ -1023,7 +1025,10 @@ ul {
.main-content {
grid-area: main;
+ min-height: 0;
+ min-width: 0;
overflow-y: auto;
+ overflow-x: hidden;
padding: var(--spacing-xl);
scroll-behavior: smooth;
position: relative;
@@ -3934,7 +3939,26 @@ input:checked + .slider::before {
filter: var(--cover-filter);
z-index: -1;
background-image: var(--bg-image);
- transition: background-image var(--transition);
+ transition:
+ background-image var(--transition),
+ filter 0.65s ease,
+ opacity 0.65s ease;
+}
+
+#fullscreen-cover-overlay::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background:
+ radial-gradient(circle at 20% 22%, rgb(var(--highlight-rgb) / 0.28), transparent 36%),
+ radial-gradient(circle at 82% 18%, rgb(255 255 255 / 0.09), transparent 28%),
+ linear-gradient(135deg, rgb(10 13 18 / 0.48), rgb(10 13 18 / 0.2) 38%, rgb(var(--highlight-rgb) / 0.12) 100%);
+ opacity: 0.36;
+ pointer-events: none;
+ z-index: 0;
+ transition:
+ opacity 0.65s ease,
+ background 0.65s ease;
}
#visualizer-container {
@@ -3944,7 +3968,13 @@ input:checked + .slider::before {
height: 100%;
z-index: 0;
pointer-events: none;
- transition: opacity 0.3s ease;
+ filter: blur(14px) saturate(0.84) brightness(0.8);
+ transform: scale(1.04);
+ opacity: 0.82;
+ transition:
+ opacity 0.65s ease,
+ filter 0.65s ease,
+ transform 0.65s ease;
}
#visualizer-canvas {
@@ -3963,6 +3993,7 @@ input:checked + .slider::before {
height: 100%;
position: relative;
padding: 1rem;
+ overflow: hidden;
}
/* UI Toggle Button for Visualizer Mode - Rightmost position */
@@ -4079,11 +4110,27 @@ input:checked + .slider::before {
/* When UI is hidden, only toggle button stays visible at right edge (when .visible class is added) */
#fullscreen-cover-overlay.ui-hidden .fullscreen-lyrics-toggle,
-#fullscreen-cover-overlay.ui-hidden #close-fullscreen-cover-btn {
+#fullscreen-cover-overlay.ui-hidden .fullscreen-top-actions {
opacity: 0;
pointer-events: none;
}
+#fullscreen-cover-overlay.ui-hidden::before,
+#fullscreen-cover-overlay.ui-hidden::after {
+ opacity: 0;
+}
+
+#fullscreen-cover-overlay.ui-hidden #visualizer-container {
+ filter: none;
+ transform: none;
+ opacity: 1;
+}
+
+body:has(#fullscreen-cover-overlay.ui-hidden.inline-lyrics) #side-panel[data-view='lyrics'] {
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.5s ease;
+}
#fullscreen-cover-overlay:not(.ui-hidden) .fullscreen-main-view,
#fullscreen-cover-overlay:not(.ui-hidden) .fullscreen-controls,
#fullscreen-cover-overlay:not(.ui-hidden) #fullscreen-next-track {
@@ -4092,6 +4139,59 @@ input:checked + .slider::before {
transition: opacity 0.5s ease;
}
+/* Auto-hide controls on idle */
+#fullscreen-cover-overlay.controls-idle .fullscreen-track-info,
+#fullscreen-cover-overlay.controls-idle .fullscreen-controls,
+#fullscreen-cover-overlay.controls-idle #fullscreen-next-track,
+#fullscreen-cover-overlay.controls-idle #toggle-ui-btn,
+#fullscreen-cover-overlay.controls-idle .fullscreen-lyrics-toggle,
+#fullscreen-cover-overlay.controls-idle .fullscreen-top-actions {
+ opacity: 0;
+ pointer-events: none;
+ transition:
+ opacity 0.6s ease,
+ transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#fullscreen-cover-overlay.controls-idle #fullscreen-cover-image {
+ transform: translateY(4rem);
+ transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#fullscreen-cover-overlay:not(.controls-idle) #fullscreen-cover-image {
+ transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#fullscreen-cover-overlay.controls-idle .fullscreen-controls {
+ transform: translateY(1.5rem);
+}
+
+#fullscreen-cover-overlay.controls-idle .fullscreen-track-info {
+ transform: translateY(0.5rem);
+}
+
+#fullscreen-cover-overlay.controls-idle #toggle-ui-btn,
+#fullscreen-cover-overlay.controls-idle .fullscreen-lyrics-toggle,
+#fullscreen-cover-overlay.controls-idle .fullscreen-top-actions {
+ transform: translateY(-0.5rem);
+}
+
+#fullscreen-cover-overlay:not(.controls-idle) .fullscreen-track-info,
+#fullscreen-cover-overlay:not(.controls-idle) .fullscreen-controls,
+#fullscreen-cover-overlay:not(.controls-idle) #fullscreen-next-track,
+#fullscreen-cover-overlay:not(.controls-idle) #toggle-ui-btn,
+#fullscreen-cover-overlay:not(.controls-idle) .fullscreen-lyrics-toggle,
+#fullscreen-cover-overlay:not(.controls-idle) .fullscreen-top-actions {
+ opacity: 1;
+ transform: translateY(0);
+ transition:
+ opacity 0.4s ease,
+ transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#fullscreen-cover-overlay.controls-idle {
+ cursor: none;
+}
#fullscreen-cover-image {
max-width: 55vw;
max-height: 45vh;
@@ -4946,7 +5046,7 @@ input:checked + .slider::before {
#download-notifications {
position: fixed;
- bottom: 120px;
+ bottom: calc(max(env(safe-area-inset-bottom), 0px) + 12px);
right: 20px;
z-index: 20000;
max-width: 350px;
@@ -6774,7 +6874,7 @@ img[src=''] {
}
#download-notifications {
- bottom: 10px;
+ bottom: calc(max(env(safe-area-inset-bottom), 0px) + 10px);
right: 10px;
left: 10px;
max-width: none;
@@ -10177,3 +10277,514 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
.contrib {
font-size: 10px;
}
+
+/* Fullscreen layout rebuild on PR 378 base */
+#fullscreen-cover-overlay .fullscreen-shell {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: stretch;
+ justify-content: center;
+ min-height: 0;
+ overflow: hidden;
+}
+
+#fullscreen-cover-overlay .fullscreen-main-view {
+ width: min(1240px, 100%);
+ height: 100%;
+ flex: 1;
+ display: grid;
+ grid-template-columns: minmax(360px, 430px) minmax(420px, 1fr);
+ gap: clamp(1.5rem, 3vw, 3rem);
+ align-items: center;
+ justify-content: center;
+ padding: clamp(4rem, 7vh, 5rem) clamp(2rem, 4vw, 3rem) clamp(3rem, 6vh, 4rem) clamp(4rem, 7vw, 6.25rem);
+ position: relative;
+ z-index: 1;
+ min-height: 0;
+ overflow: hidden;
+}
+
+#fullscreen-cover-overlay .fullscreen-media-column,
+#fullscreen-cover-overlay .fullscreen-lyrics-pane {
+ min-height: 0;
+}
+
+#fullscreen-cover-overlay .fullscreen-media-column {
+ width: min(420px, 100%);
+ display: flex;
+ flex-direction: column;
+ gap: 0.95rem;
+ justify-self: center;
+ transform: translateX(clamp(0.75rem, 1.2vw, 1.4rem));
+}
+
+#fullscreen-cover-overlay .fullscreen-artwork-card {
+ width: min(420px, 100%);
+ aspect-ratio: 1 / 1;
+ border-radius: 18px;
+ overflow: hidden;
+ box-shadow: 0 28px 80px rgba(0, 0, 0, 0.26);
+}
+
+#fullscreen-cover-overlay #fullscreen-cover-image {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ max-width: none;
+ max-height: none;
+ object-fit: cover;
+ border-radius: 18px;
+ transform: none !important;
+}
+
+#fullscreen-cover-overlay .fullscreen-track-info {
+ width: min(420px, 100%);
+ display: block;
+ text-align: left;
+ max-width: none;
+ padding: 0.15rem 0 0;
+ background: none;
+ border: 0;
+ box-shadow: none;
+ backdrop-filter: none;
+}
+
+#fullscreen-cover-overlay .fullscreen-track-text {
+ min-width: 0;
+}
+
+#fullscreen-cover-overlay #fullscreen-track-title {
+ margin: 0;
+ font-size: clamp(1.15rem, 1.5vw, 1.42rem);
+ line-height: 1.08;
+ letter-spacing: -0.03em;
+}
+
+#fullscreen-cover-overlay #fullscreen-track-artist {
+ margin: 0.12rem 0 0;
+ font-size: 0.94rem;
+ color: rgb(255 255 255 / 0.74);
+}
+
+#fullscreen-cover-overlay #toggle-fullscreen-lyrics-btn,
+#fullscreen-cover-overlay .fullscreen-lyrics-toggle {
+ display: none !important;
+}
+
+#fullscreen-cover-overlay .fullscreen-actions {
+ display: flex !important;
+ align-items: center;
+ gap: 0.5rem;
+ margin-top: 0.9rem;
+}
+
+#fullscreen-cover-overlay .fullscreen-actions .btn-icon {
+ width: 38px;
+ height: 38px;
+ padding: 0;
+ border-radius: 999px;
+ color: rgb(255 255 255 / 0.74);
+ background: transparent;
+ transition:
+ color 0.2s ease,
+ background-color 0.2s ease,
+ transform 0.2s ease;
+}
+
+#fullscreen-cover-overlay .fullscreen-actions .btn-icon:hover {
+ color: rgb(255 255 255 / 0.96);
+ background: rgb(255 255 255 / 0.08);
+ transform: scale(1.03);
+}
+
+#fullscreen-cover-overlay #fullscreen-next-track {
+ display: flex;
+ align-items: center;
+ gap: 0.45rem;
+ margin-top: 0.85rem;
+ color: rgb(255 255 255 / 0.56);
+}
+
+#fullscreen-cover-overlay #fullscreen-next-track .label {
+ font-size: 0.72rem;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+#fullscreen-cover-overlay #fullscreen-next-track .value {
+ font-size: 0.84rem;
+ color: rgb(255 255 255 / 0.74);
+}
+
+#fullscreen-cover-overlay .fullscreen-top-actions {
+ position: absolute;
+ top: 1.25rem;
+ left: calc(1.5rem + env(safe-area-inset-left));
+ right: auto;
+ display: flex;
+ align-items: center;
+ gap: 0.4rem;
+ z-index: 12;
+}
+
+#fullscreen-cover-overlay .fullscreen-top-actions button {
+ width: 40px;
+ height: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: none;
+ border-radius: 999px;
+ padding: 0;
+ background: rgb(9 12 18 / 0.34);
+ color: rgb(255 255 255 / 0.72);
+ backdrop-filter: blur(10px);
+ transition:
+ color 0.2s ease,
+ background-color 0.2s ease,
+ opacity 0.2s ease;
+}
+
+#fullscreen-cover-overlay .fullscreen-top-actions #fs-visualizer-btn {
+ order: 2;
+}
+
+#fullscreen-cover-overlay .fullscreen-top-actions #fs-visualizer-btn,
+#fullscreen-cover-overlay .fullscreen-top-actions #close-fullscreen-cover-btn {
+ position: static;
+ top: auto;
+ right: auto;
+ left: auto;
+ bottom: auto;
+ margin: 0;
+ opacity: 1;
+}
+
+#fullscreen-cover-overlay .fullscreen-top-actions #close-fullscreen-cover-btn {
+ order: 1;
+}
+
+#fullscreen-cover-overlay #toggle-ui-btn {
+ top: 1.25rem;
+ left: calc(80px + 2.3rem + env(safe-area-inset-left));
+ right: auto;
+ width: 40px;
+ height: 40px;
+ background: rgb(9 12 18 / 0.34);
+ color: rgb(255 255 255 / 0.72);
+ backdrop-filter: blur(10px);
+ opacity: 1;
+ z-index: 12;
+}
+
+#fullscreen-cover-overlay .fullscreen-controls {
+ width: min(420px, 100%);
+ margin-top: 0;
+ align-items: center;
+ gap: 0.85rem;
+ position: relative;
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons {
+ width: 100%;
+ justify-content: center;
+ gap: 0.4rem;
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons button {
+ width: 40px;
+ height: 40px;
+ color: rgb(255 255 255 / 0.72);
+ border-radius: 999px;
+ padding: 0;
+ transition:
+ color 0.2s ease,
+ transform 0.2s ease,
+ background-color 0.2s ease,
+ opacity 0.2s ease;
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons button:hover {
+ color: rgb(255 255 255 / 0.94);
+ background: rgb(255 255 255 / 0.08);
+ transform: scale(1.04);
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons button.active {
+ color: rgb(var(--highlight-rgb) / 0.98);
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn {
+ width: 54px;
+ height: 54px;
+ background: rgb(255 255 255 / 0.96);
+ color: rgb(11 15 21 / 0.92);
+ box-shadow: 0 12px 28px rgb(0 0 0 / 0.2);
+}
+
+#fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn:hover {
+ background: rgb(255 255 255 / 1);
+ transform: scale(1.02);
+}
+
+#fullscreen-cover-overlay .fullscreen-volume-container {
+ width: 238px;
+ max-width: 100%;
+ align-self: center;
+ justify-content: center;
+ margin-top: 0.2rem;
+ margin-inline: auto;
+ position: relative;
+}
+
+#fullscreen-cover-overlay .fs-visualizer-btn,
+#fullscreen-cover-overlay .fs-volume-btn {
+ width: 30px;
+ height: 30px;
+ padding: 0;
+ color: rgb(255 255 255 / 0.62);
+}
+
+#fullscreen-cover-overlay .fs-visualizer-btn:hover,
+#fullscreen-cover-overlay .fs-volume-btn:hover {
+ background: transparent;
+ color: rgb(255 255 255 / 0.9);
+}
+
+#fullscreen-cover-overlay .fs-visualizer-btn.active {
+ color: rgb(var(--highlight-rgb) / 0.96);
+}
+
+#fullscreen-cover-overlay .fs-volume-btn {
+ position: absolute;
+ left: -2.5rem;
+ top: 50%;
+ transform: translateY(-50%);
+}
+
+#fullscreen-cover-overlay .fs-volume-btn:hover {
+ transform: translateY(-50%);
+}
+
+#fullscreen-cover-overlay .fs-volume-bar {
+ width: 238px;
+ height: 4px;
+ background: rgb(255 255 255 / 0.24);
+ margin-inline: auto;
+}
+
+#fullscreen-cover-overlay .fs-volume-bar:hover {
+ height: 4px;
+}
+
+#fullscreen-cover-overlay .fs-volume-fill,
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-fill {
+ background: rgb(255 255 255 / 0.92);
+}
+
+#fullscreen-cover-overlay .fullscreen-progress-container {
+ color: rgb(255 255 255 / 0.62);
+ font-size: 0.78rem;
+}
+
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-bar {
+ height: 4px;
+ background: rgb(255 255 255 / 0.2);
+}
+
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-bar:hover {
+ height: 4px;
+}
+
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-bar:hover .progress-fill,
+#fullscreen-cover-overlay .fs-volume-bar:hover .fs-volume-fill {
+ background: rgb(var(--highlight-rgb) / 0.94);
+}
+
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-bar:hover .progress-fill::after,
+#fullscreen-cover-overlay .fullscreen-progress-container .progress-bar:active .progress-fill::after,
+#fullscreen-cover-overlay .fs-volume-bar:hover .fs-volume-fill::after,
+#fullscreen-cover-overlay .fs-volume-bar:active .fs-volume-fill::after {
+ width: 10px;
+ height: 10px;
+ box-shadow: 0 4px 12px rgb(0 0 0 / 0.28);
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-pane {
+ display: flex;
+ align-items: stretch;
+ justify-content: flex-start;
+ overflow: hidden;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-shell,
+#fullscreen-cover-overlay .fullscreen-lyrics-content,
+#fullscreen-cover-overlay .fullscreen-lyrics-content am-lyrics {
+ background: transparent !important;
+ border: 0 !important;
+ box-shadow: none !important;
+ outline: none !important;
+ backdrop-filter: none !important;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-shell {
+ width: min(860px, 100%);
+ min-height: 0;
+ margin-left: clamp(4rem, 8vw, 8rem);
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-content {
+ min-height: 0;
+ height: 100%;
+ position: relative;
+ padding-left: clamp(2.5rem, 4vw, 4rem);
+ mask-image: none;
+ overflow: visible;
+ scrollbar-width: none;
+ -ms-overflow-style: none;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-content::-webkit-scrollbar {
+ display: none;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-content am-lyrics {
+ --am-lyrics-highlight-color: #f6f4ef;
+ --lyrics-scroll-padding-top: 18%;
+ --lyplus-blur-amount: 0.16em;
+ --lyplus-blur-amount-near: 0.085em;
+ height: 100%;
+ width: 100%;
+ font-family:
+ 'SF Pro Display',
+ Inter,
+ sans-serif;
+ --lyplus-font-size-base: clamp(34px, 3vw, 52px);
+ --lyplus-padding-line: 8px;
+ --lyplus-text-color: rgba(246, 244, 239, 0.08);
+ --lyplus-active-color: #f6f4ef;
+ line-height: 1.32;
+ letter-spacing: -0.04em;
+ font-weight: 600;
+ isolation: isolate;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-content::after {
+ content: none;
+}
+
+#fullscreen-cover-overlay .fullscreen-lyrics-empty,
+#fullscreen-cover-overlay .fullscreen-lyrics-content .lyrics-loading,
+#fullscreen-cover-overlay .fullscreen-lyrics-content .lyrics-error {
+ padding: clamp(5rem, 14vh, 7rem) 0 0 clamp(2rem, 5vw, 4.5rem);
+ background: none;
+ border: 0;
+}
+
+#fullscreen-cover-overlay.lyrics-unavailable .fullscreen-lyrics-pane {
+ opacity: 0.55;
+}
+
+@media (max-width: 980px) {
+ #fullscreen-cover-overlay .fullscreen-main-view {
+ grid-template-columns: 1fr;
+ width: min(760px, 100%);
+ gap: 1rem;
+ align-items: start;
+ padding:
+ calc(4.5rem + env(safe-area-inset-top))
+ clamp(1rem, 4vw, 1.5rem)
+ calc(1.5rem + env(safe-area-inset-bottom))
+ clamp(1rem, 4vw, 1.5rem);
+ }
+
+ #fullscreen-cover-overlay .fullscreen-media-column {
+ justify-self: center;
+ transform: none;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-lyrics-pane {
+ display: none;
+ }
+}
+
+@media (max-width: 768px) {
+ #fullscreen-cover-overlay .fullscreen-cover-content {
+ padding: 0.75rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom));
+ }
+
+ #fullscreen-cover-overlay .fullscreen-top-actions {
+ top: calc(0.75rem + env(safe-area-inset-top));
+ left: calc(1rem + env(safe-area-inset-left));
+ gap: 0.35rem;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-top-actions button,
+ #fullscreen-cover-overlay #toggle-ui-btn {
+ width: 44px;
+ height: 44px;
+ background: rgb(9 12 18 / 0.5);
+ }
+
+ #fullscreen-cover-overlay #toggle-ui-btn {
+ top: calc(0.75rem + env(safe-area-inset-top));
+ left: calc(88px + 1.25rem + env(safe-area-inset-left));
+ }
+
+ #fullscreen-cover-overlay .fullscreen-main-view {
+ width: 100%;
+ gap: 0.85rem;
+ padding:
+ calc(4.25rem + env(safe-area-inset-top))
+ 0.75rem
+ calc(1.5rem + env(safe-area-inset-bottom))
+ 0.75rem;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-track-info,
+ #fullscreen-cover-overlay .fullscreen-controls,
+ #fullscreen-cover-overlay .fullscreen-media-column {
+ width: min(100%, 460px);
+ }
+
+ #fullscreen-cover-overlay .fullscreen-actions {
+ width: 100%;
+ flex-wrap: wrap;
+ gap: 0.45rem;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-actions .btn-icon {
+ background: rgb(255 255 255 / 0.06);
+ }
+
+ #fullscreen-cover-overlay .fullscreen-progress-container {
+ gap: 0.65rem;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-buttons {
+ gap: 0.2rem;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-buttons button {
+ width: 38px;
+ height: 38px;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn {
+ width: 52px;
+ height: 52px;
+ }
+
+ #fullscreen-cover-overlay .fullscreen-volume-container {
+ width: min(220px, calc(100% - 2.75rem));
+ }
+
+ #fullscreen-cover-overlay .fs-volume-btn {
+ left: -2.25rem;
+ }
+
+ #fullscreen-cover-overlay .fs-volume-bar {
+ width: 100%;
+ }
+}