diff --git a/functions/album/[id].js b/functions/album/[id].js index b75c0bf..1b002f5 100644 --- a/functions/album/[id].js +++ b/functions/album/[id].js @@ -47,52 +47,11 @@ class TidalAPI { class ServerAPI { constructor() { - this.INSTANCES_URLS = [ - 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', - 'https://tidal-uptime.props-76styles.workers.dev/', - ]; - this.apiInstances = null; + this.apiInstances = ['https://hifi.geeked.wtf']; } async getInstances() { - if (this.apiInstances) return this.apiInstances; - - let data = null; - const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5); - - for (const url of urls) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - data = await response.json(); - break; - } catch (error) { - console.warn(`Failed to fetch from ${url}:`, error); - } - } - - if (data) { - this.apiInstances = (data.api || []) - .map((item) => item.url || item) - .filter((url) => !/\.squid\.wtf/i.test(url)); - return this.apiInstances; - } - - console.error('Failed to load instances from all uptime APIs'); - return [ - 'https://hifi.geeked.wtf', - 'https://eu-central.monochrome.tf', - 'https://us-west.monochrome.tf', - 'https://arran.monochrome.tf', - 'https://api.monochrome.tf', - 'https://monochrome-api.samidy.com', - 'https://maus.qqdl.site', - 'https://vogel.qqdl.site', - 'https://katze.qqdl.site', - 'https://hund.qqdl.site', - 'https://tidal.kinoplus.online', - 'https://wolf.qqdl.site', - ]; + return this.apiInstances; } async fetchWithRetry(relativePath) { diff --git a/functions/artist/[id].js b/functions/artist/[id].js index 1c62591..a924773 100644 --- a/functions/artist/[id].js +++ b/functions/artist/[id].js @@ -47,50 +47,11 @@ class TidalAPI { class ServerAPI { constructor() { - this.INSTANCES_URLS = [ - 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', - 'https://tidal-uptime.props-76styles.workers.dev/', - ]; - this.apiInstances = null; + this.apiInstances = ['https://hifi.geeked.wtf']; } async getInstances() { - if (this.apiInstances) return this.apiInstances; - - let data = null; - const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5); - - for (const url of urls) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - data = await response.json(); - break; - } catch (error) { - console.warn(`Failed to fetch from ${url}:`, error); - } - } - - if (data) { - this.apiInstances = (data.api || []).map((item) => item.url || item); - return this.apiInstances; - } - - console.error('Failed to load instances from all uptime APIs'); - return [ - 'https://eu-central.monochrome.tf', - 'https://us-west.monochrome.tf', - 'https://arran.monochrome.tf', - 'https://triton.squid.wtf', - 'https://api.monochrome.tf', - 'https://monochrome-api.samidy.com', - 'https://maus.qqdl.site', - 'https://vogel.qqdl.site', - 'https://katze.qqdl.site', - 'https://hund.qqdl.site', - 'https://tidal.kinoplus.online', - 'https://wolf.qqdl.site', - ]; + return this.apiInstances; } async fetchWithRetry(relativePath) { diff --git a/functions/playlist/[id].js b/functions/playlist/[id].js index 15fe6f6..4e1dd21 100644 --- a/functions/playlist/[id].js +++ b/functions/playlist/[id].js @@ -47,51 +47,11 @@ class TidalAPI { class ServerAPI { constructor() { - this.INSTANCES_URLS = [ - 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', - 'https://tidal-uptime.props-76styles.workers.dev/', - ]; - this.apiInstances = null; + this.apiInstances = ['https://hifi.geeked.wtf']; } async getInstances() { - if (this.apiInstances) return this.apiInstances; - - let data = null; - const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5); - - for (const url of urls) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - data = await response.json(); - break; - } catch (error) { - console.warn(`Failed to fetch from ${url}:`, error); - } - } - - if (data) { - this.apiInstances = (data.api || []) - .map((item) => item.url || item) - .filter((url) => !/\.squid\.wtf/i.test(url)); - return this.apiInstances; - } - - console.error('Failed to load instances from all uptime APIs'); - return [ - 'https://eu-central.monochrome.tf', - 'https://us-west.monochrome.tf', - 'https://arran.monochrome.tf', - 'https://api.monochrome.tf', - 'https://monochrome-api.samidy.com', - 'https://maus.qqdl.site', - 'https://vogel.qqdl.site', - 'https://katze.qqdl.site', - 'https://hund.qqdl.site', - 'https://tidal.kinoplus.online', - 'https://wolf.qqdl.site', - ]; + return this.apiInstances; } async fetchWithRetry(relativePath) { diff --git a/functions/track/[id].js b/functions/track/[id].js index cf50d44..a274b12 100644 --- a/functions/track/[id].js +++ b/functions/track/[id].js @@ -69,50 +69,11 @@ class TidalAPI { class ServerAPI { constructor() { - this.INSTANCES_URLS = [ - 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', - 'https://tidal-uptime.props-76styles.workers.dev/', - ]; - this.apiInstances = null; + this.apiInstances = ['https://hifi.geeked.wtf']; } async getInstances() { - if (this.apiInstances) return this.apiInstances; - - let data = null; - const urls = [...this.INSTANCES_URLS].sort(() => Math.random() - 0.5); - - for (const url of urls) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - data = await response.json(); - break; - } catch (error) { - console.warn(`Failed to fetch from ${url}:`, error); - } - } - - if (data) { - this.apiInstances = (data.api || []).map((item) => item.url || item); - return this.apiInstances; - } - - console.error('Failed to load instances from all uptime APIs'); - return [ - 'https://eu-central.monochrome.tf', - 'https://us-west.monochrome.tf', - 'https://arran.monochrome.tf', - 'https://triton.squid.wtf', - 'https://api.monochrome.tf', - 'https://monochrome-api.samidy.com', - 'https://maus.qqdl.site', - 'https://vogel.qqdl.site', - 'https://katze.qqdl.site', - 'https://hund.qqdl.site', - 'https://tidal.kinoplus.online', - 'https://wolf.qqdl.site', - ]; + return this.apiInstances; } async fetchWithRetry(relativePath) { diff --git a/index.html b/index.html index 2898360..eb274e0 100644 --- a/index.html +++ b/index.html @@ -138,13 +138,14 @@ z-index: 0; " > + -
+ diff --git a/js/lyrics.js b/js/lyrics.js index 62f245e..59307ca 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -992,12 +992,17 @@ function applyFullscreenLyricsShadowTweaks(amLyrics, container) { } .lyrics-line { + transform-origin: left center; 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:not(.active):not(.pre-active) { + opacity: 0.44; + } + .lyrics-line-container { transition: transform 0.72s cubic-bezier(0.22, 1, 0.36, 1), @@ -1012,6 +1017,10 @@ function applyFullscreenLyricsShadowTweaks(amLyrics, container) { background-color 0.22s ease, color 0.22s ease !important; } + + .lyrics-line.active .lyrics-line-container { + transform: scale(1.015); + } `; return true; diff --git a/js/storage.js b/js/storage.js index edce8ba..743c42c 100644 --- a/js/storage.js +++ b/js/storage.js @@ -4,11 +4,11 @@ import { SVG_RIGHT_ARROW } from './icons'; export const apiSettings = { STORAGE_KEY: 'monochrome-api-instances-v9', - INSTANCES_URLS: [ - 'https://tidal-uptime.jiffy-puffs-1j.workers.dev/', - 'https://tidal-uptime.props-76styles.workers.dev/', - ], - defaultInstances: { api: [], streaming: [] }, + PINNED_INSTANCE: Object.freeze({ url: 'https://hifi.geeked.wtf', version: '2.7' }), + defaultInstances: { + api: [{ url: 'https://hifi.geeked.wtf', version: '2.7' }], + streaming: [{ url: 'https://hifi.geeked.wtf', version: '2.7' }], + }, userInstances: null, instancesLoaded: false, _loadPromise: null, @@ -29,136 +29,13 @@ export const apiSettings = { }, async loadInstancesFromGitHub() { - if (this.instancesLoaded) { - return this.defaultInstances; - } - - if (this._loadPromise) { - return this._loadPromise; - } - - this._loadPromise = (async () => { - const cachedData = localStorage.getItem(this.STORAGE_KEY); - if (cachedData) { - try { - const parsed = JSON.parse(cachedData); - const now = Date.now(); - // Check if cached data is less than 15 minutes old - if (parsed.timestamp && now - parsed.timestamp < 15 * 60 * 1000) { - this.defaultInstances = parsed.data; - this.instancesLoaded = true; - this._loadPromise = null; - return this.defaultInstances; - } - } catch (e) { - console.warn('Failed to parse cached instances:', e); - } - } - - let data = null; - let fetchError = null; - - // Prefer first URL, only try others as fallback - const urls = [...this.INSTANCES_URLS]; - - for (const url of urls) { - try { - const response = await fetch(url); - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - data = await response.json(); - break; // Success, exit loop - } catch (error) { - console.warn(`Failed to fetch from ${url}:`, error); - fetchError = error; - } - } - - if (!data) { - console.error('Failed to load instances from all uptime APIs:', fetchError); - this.defaultInstances = { - api: [ - { url: 'https://hifi.geeked.wtf', version: '2.7' }, - { url: 'https://eu-central.monochrome.tf', version: '2.7' }, - { url: 'https://us-west.monochrome.tf', version: '2.7' }, - { url: 'https://api.monochrome.tf', version: '2.5' }, - { url: 'https://monochrome-api.samidy.com', version: '2.3' }, - { url: 'https://maus.qqdl.site', version: '2.6' }, - { url: 'https://vogel.qqdl.site', version: '2.6' }, - { url: 'https://katze.qqdl.site', version: '2.6' }, - { url: 'https://hund.qqdl.site', version: '2.6' }, - { url: 'https://tidal.kinoplus.online', version: '2.2' }, - { url: 'https://wolf.qqdl.site', version: '2.2' }, - ], - streaming: [ - { url: 'https://hifi.geeked.wtf', version: '2.7' }, - { url: 'https://maus.qqdl.site', version: '2.6' }, - { url: 'https://vogel.qqdl.site', version: '2.6' }, - { url: 'https://katze.qqdl.site', version: '2.6' }, - { url: 'https://hund.qqdl.site', version: '2.6' }, - { url: 'https://wolf.qqdl.site', version: '2.6' }, - ], - }; - this.instancesLoaded = true; - this._loadPromise = null; - return this.defaultInstances; - } - - let groupedInstances = { api: [], streaming: [] }; - - const isBlockedInstance = (item) => { - const url = typeof item === 'string' ? item : item.url; - return url && /\.squid\.wtf/i.test(url); - }; - - if (data.api && Array.isArray(data.api)) { - groupedInstances.api = data.api.filter((item) => !isBlockedInstance(item)); - } - - if (data.streaming && Array.isArray(data.streaming)) { - groupedInstances.streaming = data.streaming.filter((item) => !isBlockedInstance(item)); - } else if (groupedInstances.api.length > 0) { - groupedInstances.streaming = [...groupedInstances.api]; - } - - this.defaultInstances = groupedInstances; - this.instancesLoaded = true; - - try { - localStorage.setItem( - this.STORAGE_KEY, - JSON.stringify({ - timestamp: Date.now(), - data: groupedInstances, - }) - ); - } catch (e) { - console.warn('Failed to cache instances:', e); - } - - this._loadPromise = null; - return groupedInstances; - })(); - - return this._loadPromise; + this.instancesLoaded = true; + return this.defaultInstances; }, async getInstances(type = 'api', _sortBySpeed = false) { - let instancesObj; - - instancesObj = await this.loadInstancesFromGitHub(); - const userInst = this._loadUserInstances(); - - const defaultUrls = instancesObj[type] || instancesObj.api || []; - const userUrls = userInst[type] || []; - - const combined = [ - ...userUrls.map((u) => (typeof u === 'string' ? { url: u, isUser: true } : { ...u, isUser: true })), - ...defaultUrls, - ]; - - if (combined.length === 0) return []; - - return combined; + const instancesObj = await this.loadInstancesFromGitHub(); + return instancesObj[type] || instancesObj.api || []; }, addUserInstance(type, url) { @@ -191,42 +68,6 @@ export const apiSettings = { this.instancesLoaded = false; this._loadPromise = null; localStorage.removeItem(this.STORAGE_KEY); - - const instances = await this.loadInstancesFromGitHub(); - - const shuffle = (array) => { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } - return array; - }; - - const prioritySort = (array) => { - const getUrl = (item) => (typeof item === 'string' ? item : item.url || ''); - const top = []; - const middle = []; - const bottom = []; - for (const item of array) { - const url = getUrl(item); - if (url.includes('hifi.geeked.wtf')) top.push(item); - else if (url.includes('.qqdl.site')) bottom.push(item); - else middle.push(item); - } - return [...top, ...shuffle(middle), ...shuffle(bottom)]; - }; - - if (instances.api && instances.api.length) { - instances.api = prioritySort([...instances.api]); - } - - if (instances.streaming && instances.streaming.length) { - instances.streaming = prioritySort([...instances.streaming]); - } - - this.saveInstances(instances); - - // Return API instances for the UI to render (default view) return this.getInstances('api'); }, saveInstances(instances, type) { diff --git a/js/ui.js b/js/ui.js index 5827d7f..164c30b 100644 --- a/js/ui.js +++ b/js/ui.js @@ -93,6 +93,8 @@ const setFullscreenUIToggleIcon = (button, visualizerOnlyMode) => { button.innerHTML = visualizerOnlyMode ? SVG_EYE(24) : SVG_EYE_OFF(24); }; +const isMobileFullscreenViewport = () => window.matchMedia('(max-width: 768px)').matches; + function sortTracks(tracks, sortType) { if (sortType === 'custom') return [...tracks]; const sorted = [...tracks]; @@ -157,6 +159,8 @@ export class UIRenderer { this.currentArtistId = null; this.fullscreenLyricsVisible = true; this.fullscreenPlaybackStateCleanup = null; + this.fullscreenDismissHandleCleanup = null; + this.fullscreenLyricsToggleCleanup = null; // Listen for dynamic color reset events window.addEventListener('reset-dynamic-color', () => { @@ -1046,9 +1050,13 @@ export class UIRenderer { let r = parseInt(hex.substr(0, 2), 16); let g = parseInt(hex.substr(2, 2), 16); let b = parseInt(hex.substr(4, 2), 16); + let fullscreenR = r; + let fullscreenG = g; + let fullscreenB = b; // Calculate perceived brightness let brightness = (r * 299 + g * 587 + b * 114) / 1000; + let fullscreenBrightness = brightness; if (isLightMode) { // In light mode, the background is white. @@ -1075,6 +1083,23 @@ export class UIRenderer { } const adjustedColor = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; + while (fullscreenBrightness < 105) { + fullscreenR = Math.min(255, Math.max(fullscreenR + 1, Math.floor(fullscreenR * 1.08))); + fullscreenG = Math.min(255, Math.max(fullscreenG + 1, Math.floor(fullscreenG * 1.08))); + fullscreenB = Math.min(255, Math.max(fullscreenB + 1, Math.floor(fullscreenB * 1.08))); + fullscreenBrightness = (fullscreenR * 299 + fullscreenG * 587 + fullscreenB * 114) / 1000; + if (fullscreenR >= 255 && fullscreenG >= 255 && fullscreenB >= 255) break; + } + while (fullscreenBrightness > 185) { + fullscreenR = Math.floor(fullscreenR * 0.92); + fullscreenG = Math.floor(fullscreenG * 0.92); + fullscreenB = Math.floor(fullscreenB * 0.92); + fullscreenBrightness = (fullscreenR * 299 + fullscreenG * 587 + fullscreenB * 114) / 1000; + } + + const fullscreenAdjustedColor = `#${fullscreenR.toString(16).padStart(2, '0')}${fullscreenG + .toString(16) + .padStart(2, '0')}${fullscreenB.toString(16).padStart(2, '0')}`; // Calculate contrast text color for buttons (text on top of the vibrant color) const foreground = brightness > 128 ? '#000000' : '#ffffff'; @@ -1086,6 +1111,8 @@ export class UIRenderer { root.style.setProperty('--highlight-rgb', `${r}, ${g}, ${b}`); root.style.setProperty('--active-highlight', adjustedColor); root.style.setProperty('--ring', adjustedColor); + root.style.setProperty('--fs-accent', fullscreenAdjustedColor); + root.style.setProperty('--fs-accent-rgb', `${fullscreenR}, ${fullscreenG}, ${fullscreenB}`); // Calculate a safe hover color let hoverColor; @@ -1108,6 +1135,8 @@ export class UIRenderer { root.style.removeProperty('--highlight-rgb'); root.style.removeProperty('--active-highlight'); root.style.removeProperty('--ring'); + root.style.removeProperty('--fs-accent'); + root.style.removeProperty('--fs-accent-rgb'); root.style.removeProperty('--track-hover-bg'); } @@ -1221,7 +1250,6 @@ export class UIRenderer { currentImage.src = coverUrl; } } - overlay.style.setProperty('--bg-image', `url('${this.api.getCoverUrl(track.album?.cover, '1280')}')`); await this.extractAndApplyColor(this.api.getCoverUrl(track.album?.cover, '80')); } @@ -1239,7 +1267,7 @@ export class UIRenderer { async showFullscreenCover(track, nextTrack, lyricsManager, activeElement) { if (!track) return; - this.fullscreenVisualizerSuppressed = true; + this.fullscreenVisualizerSuppressed = isMobileFullscreenViewport(); if (window.location.hash !== '#fullscreen') { window.history.pushState({ fullscreen: true }, '', '#fullscreen'); } @@ -1261,18 +1289,21 @@ export class UIRenderer { const canRenderLyrics = Boolean(lyricsManager && activeElement && lyricsPane && lyricsContent && track.type !== 'video'); if (canRenderLyrics) { - lyricsToggleBtn.style.display = 'none'; + this.fullscreenLyricsVisible = true; + if (lyricsToggleBtn) lyricsToggleBtn.style.removeProperty('display'); overlay.classList.remove('lyrics-unavailable'); clearFullscreenLyricsSync(lyricsContent); await renderLyricsInFullscreen(track, activeElement, lyricsManager, lyricsContent); } else { - lyricsToggleBtn.style.display = 'none'; + this.fullscreenLyricsVisible = false; + if (lyricsToggleBtn) lyricsToggleBtn.style.display = 'none'; overlay.classList.add('lyrics-unavailable'); if (lyricsContent) { clearFullscreenLyricsSync(lyricsContent); lyricsContent.innerHTML = '
Lyrics are not available for this track.
'; } } + this.updateFullscreenLyricsVisibility(overlay); const playerBar = document.querySelector('.now-playing-bar'); if (playerBar) playerBar.style.display = 'none'; @@ -1303,9 +1334,64 @@ export class UIRenderer { this.setupUIToggleButton(overlay); this.setupControlsAutoHide(overlay); this.setupFullscreenSidePanelSync(overlay); + this.setupFullscreenDismissHandle(overlay); + this.setupFullscreenLyricsToggle(overlay); await this.refreshFullscreenVisualizerState(activeElement); } + updateFullscreenLyricsVisibility(overlay = document.getElementById('fullscreen-cover-overlay')) { + if (!overlay) return; + + const lyricsToggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn'); + const lyricsUnavailable = overlay.classList.contains('lyrics-unavailable'); + const shouldShowLyrics = this.fullscreenLyricsVisible && !lyricsUnavailable; + + overlay.classList.toggle('lyrics-hidden', !shouldShowLyrics); + + if (lyricsToggleBtn) { + lyricsToggleBtn.classList.toggle('active', shouldShowLyrics); + lyricsToggleBtn.title = shouldShowLyrics ? 'Hide Lyrics' : 'Show Lyrics'; + lyricsToggleBtn.setAttribute('aria-pressed', shouldShowLyrics ? 'true' : 'false'); + if (lyricsUnavailable) { + lyricsToggleBtn.style.display = 'none'; + } else { + lyricsToggleBtn.style.removeProperty('display'); + } + } + } + + async dismissFullscreenCover({ animate = true } = {}) { + const overlay = document.getElementById('fullscreen-cover-overlay'); + if (!overlay || overlay.style.display === 'none') return; + + if (animate) { + await new Promise((resolve) => { + const finish = () => { + overlay.removeEventListener('transitionend', handleTransitionEnd); + overlay.classList.remove('fullscreen-dragging', 'fullscreen-dismissing'); + overlay.style.removeProperty('--fullscreen-drag-offset'); + overlay.style.removeProperty('--fullscreen-drag-progress'); + resolve(); + }; + + const handleTransitionEnd = (event) => { + if (event.target !== overlay.querySelector('.fullscreen-cover-content')) return; + finish(); + }; + + overlay.addEventListener('transitionend', handleTransitionEnd); + overlay.classList.add('fullscreen-dismissing'); + window.setTimeout(finish, 280); + }); + } + + this.closeFullscreenCover(); + + if (window.location.hash === '#fullscreen') { + window.history.back(); + } + } + closeFullscreenCover() { const overlay = document.getElementById('fullscreen-cover-overlay'); const coverImage = document.getElementById('fullscreen-cover-image'); @@ -1318,7 +1404,16 @@ export class UIRenderer { lyricsContent.innerHTML = '
Lyrics appear here.
'; } overlay.style.display = 'none'; - overlay.classList.remove('visualizer-active', 'ui-hidden', 'fullscreen-cover-no-round', 'fullscreen-paused'); + overlay.classList.remove( + 'visualizer-active', + 'ui-hidden', + 'fullscreen-cover-no-round', + 'fullscreen-paused', + 'fullscreen-dragging', + 'fullscreen-dismissing' + ); + overlay.style.removeProperty('--fullscreen-drag-offset'); + overlay.style.removeProperty('--fullscreen-drag-progress'); const playerBar = document.querySelector('.now-playing-bar'); if (playerBar) playerBar.style.removeProperty('display'); @@ -1379,6 +1474,16 @@ export class UIRenderer { this.fullscreenSidePanelSyncCleanup(); this.fullscreenSidePanelSyncCleanup = null; } + + if (this.fullscreenDismissHandleCleanup) { + this.fullscreenDismissHandleCleanup(); + this.fullscreenDismissHandleCleanup = null; + } + + if (this.fullscreenLyricsToggleCleanup) { + this.fullscreenLyricsToggleCleanup(); + this.fullscreenLyricsToggleCleanup = null; + } } async startFullscreenVisualizer(activeElement, overlay) { @@ -1393,6 +1498,7 @@ export class UIRenderer { } if (this.visualizer) { + this.visualizer.applyPresetOverride('kawarp'); await this.visualizer.start(); overlay.classList.add('visualizer-active'); } @@ -1438,7 +1544,8 @@ export class UIRenderer { 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; + const enabled = + !isVideoTrack && !this.fullscreenVisualizerSuppressed && !isMobileFullscreenViewport(); if (!overlay) return; @@ -1604,6 +1711,136 @@ export class UIRenderer { }; } + setupFullscreenDismissHandle(overlay) { + if (this.fullscreenDismissHandleCleanup) { + this.fullscreenDismissHandleCleanup(); + this.fullscreenDismissHandleCleanup = null; + } + + const handle = document.getElementById('fullscreen-dismiss-handle'); + if (!handle) return; + + let activePointerId = null; + let startY = 0; + let startX = 0; + let lastY = 0; + let lastTimestamp = 0; + let velocityY = 0; + let hasDragged = false; + + const resetDragState = () => { + activePointerId = null; + hasDragged = false; + overlay.classList.remove('fullscreen-dragging'); + overlay.style.removeProperty('--fullscreen-drag-offset'); + overlay.style.removeProperty('--fullscreen-drag-progress'); + }; + + const onPointerDown = (event) => { + if (!isMobileFullscreenViewport()) return; + + activePointerId = event.pointerId; + startY = event.clientY; + startX = event.clientX; + lastY = event.clientY; + lastTimestamp = event.timeStamp; + velocityY = 0; + hasDragged = false; + overlay.classList.add('fullscreen-dragging'); + handle.setPointerCapture(event.pointerId); + }; + + const onPointerMove = (event) => { + if (event.pointerId !== activePointerId) return; + + const deltaY = Math.max(0, event.clientY - startY); + const deltaX = Math.abs(event.clientX - startX); + + if (!hasDragged && deltaX > deltaY) { + resetDragState(); + return; + } + + hasDragged = true; + event.preventDefault(); + + const elapsed = Math.max(1, event.timeStamp - lastTimestamp); + velocityY = (event.clientY - lastY) / elapsed; + lastY = event.clientY; + lastTimestamp = event.timeStamp; + + const progress = Math.min(deltaY / Math.max(window.innerHeight * 0.32, 1), 1); + overlay.style.setProperty('--fullscreen-drag-offset', `${deltaY}px`); + overlay.style.setProperty('--fullscreen-drag-progress', progress.toFixed(3)); + }; + + const onPointerEnd = async (event) => { + if (event.pointerId !== activePointerId) return; + + const deltaY = Math.max(0, event.clientY - startY); + const shouldDismiss = hasDragged && (deltaY > 96 || velocityY > 0.55); + + if (handle.hasPointerCapture(event.pointerId)) { + handle.releasePointerCapture(event.pointerId); + } + + if (shouldDismiss) { + await this.dismissFullscreenCover(); + return; + } + + resetDragState(); + }; + + const onClick = async (event) => { + if (!isMobileFullscreenViewport() || hasDragged) return; + event.preventDefault(); + await this.dismissFullscreenCover(); + }; + + handle.addEventListener('pointerdown', onPointerDown); + handle.addEventListener('pointermove', onPointerMove); + handle.addEventListener('pointerup', onPointerEnd); + handle.addEventListener('pointercancel', onPointerEnd); + handle.addEventListener('click', onClick); + + this.fullscreenDismissHandleCleanup = () => { + handle.removeEventListener('pointerdown', onPointerDown); + handle.removeEventListener('pointermove', onPointerMove); + handle.removeEventListener('pointerup', onPointerEnd); + handle.removeEventListener('pointercancel', onPointerEnd); + handle.removeEventListener('click', onClick); + overlay.classList.remove('fullscreen-dragging'); + overlay.style.removeProperty('--fullscreen-drag-offset'); + overlay.style.removeProperty('--fullscreen-drag-progress'); + }; + } + + setupFullscreenLyricsToggle(overlay) { + if (this.fullscreenLyricsToggleCleanup) { + this.fullscreenLyricsToggleCleanup(); + this.fullscreenLyricsToggleCleanup = null; + } + + const toggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn'); + if (!toggleBtn) return; + + const handleToggle = (event) => { + event.preventDefault(); + event.stopPropagation(); + if (overlay.classList.contains('lyrics-unavailable')) return; + this.fullscreenLyricsVisible = !this.fullscreenLyricsVisible; + this.updateFullscreenLyricsVisibility(overlay); + }; + + toggleBtn.addEventListener('click', handleToggle); + this.updateFullscreenLyricsVisibility(overlay); + + this.fullscreenLyricsToggleCleanup = () => { + toggleBtn.removeEventListener('click', handleToggle); + }; + } + setupFullscreenControls() { const playBtn = document.getElementById('fs-play-pause-btn'); const prevBtn = document.getElementById('fs-prev-btn'); @@ -1673,16 +1910,7 @@ 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); - } + this.fullscreenVisualizerSuppressed = !this.fullscreenVisualizerSuppressed; await this.refreshFullscreenVisualizerState(this.player.activeElement); }; } diff --git a/js/visualizer.js b/js/visualizer.js index 49017d2..ff8a988 100644 --- a/js/visualizer.js +++ b/js/visualizer.js @@ -318,4 +318,16 @@ export class Visualizer { }); } } + + applyPresetOverride(key) { + if (!this.presets?.[key] || this.activePresetKey === key) return; + + if (this.activePreset?.destroy) { + this.activePreset.destroy(); + } + + this._currentContextType = undefined; + this.ctx = null; + this.activePresetKey = key; + } } diff --git a/public/instances.json b/public/instances.json index d5a8bb9..b9fce85 100644 --- a/public/instances.json +++ b/public/instances.json @@ -1,25 +1,8 @@ { "api": [ - "https://eu-central.monochrome.tf", - "https://us-west.monochrome.tf", - "https://arran.monochrome.tf", - "https://api.monochrome.tf/", - "https://monochrome-api.samidy.com", - "https://triton.squid.wtf", - "https://wolf.qqdl.site", - "https://maus.qqdl.site", - "https://vogel.qqdl.site", - "https://hund.qqdl.site", - "https://tidal.kinoplus.online" + "https://hifi.geeked.wtf" ], "streaming": [ - "https://arran.monochrome.tf", - "https://triton.squid.wtf", - "https://wolf.qqdl.site", - "https://maus.qqdl.site", - "https://vogel.qqdl.site", - "https://katze.qqdl.site", - "https://hund.qqdl.site", - "https://hifi.p1nkhamster.xyz/" + "https://hifi.geeked.wtf" ] } diff --git a/styles.css b/styles.css index 57d66e9..7b32f2e 100644 --- a/styles.css +++ b/styles.css @@ -3920,29 +3920,25 @@ input:checked + .slider::before { justify-content: center; animation: fade-in 0.3s ease; overflow: hidden; - background-color: var(--background); - - /* Use a CSS variable for the image so we can set it in JS */ - --bg-image: none; + background-color: rgb(11 13 17); /* Reserve space above taskbar / system UI so volume controls stay visible (fixes #322) */ padding-bottom: max(env(safe-area-inset-bottom), 1.5rem); + --fullscreen-drag-progress: 0; + --fs-accent-rgb: var(--highlight-rgb); } #fullscreen-cover-overlay::before { content: ''; position: absolute; - inset: -20px; - background-size: cover; - background-position: center; - background-repeat: no-repeat; - filter: var(--cover-filter); + inset: 0; + background: + radial-gradient(circle at 50% 50%, rgb(255 255 255 / 0.035), transparent 58%), + linear-gradient(180deg, rgb(6 8 12 / 0.12), rgb(6 8 12 / 0.34)); z-index: -1; - background-image: var(--bg-image); transition: - background-image var(--transition), - filter 0.65s ease, opacity 0.65s ease; + opacity: calc(1 - (var(--fullscreen-drag-progress, 0) * 0.32)); } #fullscreen-cover-overlay::after { @@ -3950,10 +3946,10 @@ input:checked + .slider::before { position: absolute; inset: 0; background: - radial-gradient(circle at 20% 22%, rgb(var(--highlight-rgb) / 0.28), transparent 36%), + radial-gradient(circle at 20% 22%, rgb(var(--fs-accent-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; + linear-gradient(135deg, rgb(10 13 18 / 0.48), rgb(10 13 18 / 0.2) 38%, rgb(var(--fs-accent-rgb) / 0.12) 100%); + opacity: calc(0.36 - (var(--fullscreen-drag-progress, 0) * 0.26)); pointer-events: none; z-index: 0; transition: @@ -3968,9 +3964,9 @@ input:checked + .slider::before { height: 100%; z-index: 0; pointer-events: none; - filter: blur(14px) saturate(0.84) brightness(0.8); - transform: scale(1.04); - opacity: 0.82; + filter: blur(8px) saturate(0.9) brightness(0.8); + transform: scale(1.03); + opacity: 0.8; transition: opacity 0.65s ease, filter 0.65s ease, @@ -3994,6 +3990,51 @@ input:checked + .slider::before { position: relative; padding: 1rem; overflow: hidden; + transform: translateY(var(--fullscreen-drag-offset, 0px)); + opacity: calc(1 - (var(--fullscreen-drag-progress, 0) * 0.16)); + transition: + transform 0.26s cubic-bezier(0.22, 1, 0.36, 1), + opacity 0.22s ease; + will-change: transform, opacity; +} + +#fullscreen-dismiss-handle { + position: absolute; + top: calc(0.75rem + env(safe-area-inset-top)); + left: 50%; + width: 3.25rem; + height: 1rem; + border: 0; + padding: 0; + margin: 0; + background: transparent; + transform: translateX(-50%); + z-index: 14; + display: none; + cursor: grab; + touch-action: none; +} + +#fullscreen-dismiss-handle::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 3rem; + height: 0.3rem; + border-radius: 999px; + transform: translate(-50%, -50%); + background: rgb(255 255 255 / 0.28); + box-shadow: 0 2px 12px rgb(0 0 0 / 0.25); +} + +#fullscreen-cover-overlay.fullscreen-dragging .fullscreen-cover-content { + transition: none; +} + +#fullscreen-cover-overlay.fullscreen-dismissing .fullscreen-cover-content { + transform: translateY(calc(100% + 3rem)); + opacity: 0; } /* UI Toggle Button for Visualizer Mode - Rightmost position */ @@ -10049,19 +10090,25 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { } #fullscreen-cover-overlay .fullscreen-main-view { - width: min(1240px, 100%); + --fs-media-column-size: minmax(340px, 430px); + --fs-lyrics-column-size: minmax(520px, 760px); + width: min(1480px, 100%); height: 100%; flex: 1; display: grid; - grid-template-columns: minmax(360px, 430px) minmax(420px, 1fr); - gap: clamp(1.5rem, 3vw, 3rem); + grid-template-columns: var(--fs-media-column-size) var(--fs-lyrics-column-size); + gap: clamp(2rem, 4vw, 4.5rem); align-items: center; justify-content: center; - padding: clamp(4rem, 7vh, 5rem) clamp(2rem, 4vw, 3rem) clamp(3rem, 6vh, 4rem) clamp(4rem, 7vw, 6.25rem); + padding: clamp(4rem, 7vh, 5rem) clamp(3rem, 6vw, 5rem) clamp(3rem, 6vh, 4rem); position: relative; z-index: 1; min-height: 0; overflow: hidden; + transition: + grid-template-columns 0.34s cubic-bezier(0.22, 1, 0.36, 1), + width 0.34s cubic-bezier(0.22, 1, 0.36, 1), + gap 0.34s cubic-bezier(0.22, 1, 0.36, 1); } #fullscreen-cover-overlay .fullscreen-media-column, @@ -10075,7 +10122,11 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { flex-direction: column; gap: 0.95rem; justify-self: center; - transform: translateX(clamp(0.75rem, 1.2vw, 1.4rem)); + transform: none; + transition: + width 0.34s cubic-bezier(0.22, 1, 0.36, 1), + transform 0.34s cubic-bezier(0.22, 1, 0.36, 1), + opacity 0.24s ease; } #fullscreen-cover-overlay .fullscreen-artwork-card { @@ -10128,7 +10179,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { #fullscreen-cover-overlay #toggle-fullscreen-lyrics-btn, #fullscreen-cover-overlay .fullscreen-lyrics-toggle { - display: none !important; + display: flex; } #fullscreen-cover-overlay .fullscreen-actions { @@ -10206,9 +10257,10 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { } #fullscreen-cover-overlay .fullscreen-top-actions #fs-visualizer-btn { - order: 2; + order: 3; } +#fullscreen-cover-overlay .fullscreen-top-actions #toggle-fullscreen-lyrics-btn, #fullscreen-cover-overlay .fullscreen-top-actions #fs-visualizer-btn, #fullscreen-cover-overlay .fullscreen-top-actions #close-fullscreen-cover-btn { position: static; @@ -10220,6 +10272,15 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { opacity: 1; } +#fullscreen-cover-overlay .fullscreen-top-actions #toggle-fullscreen-lyrics-btn { + order: 2; +} + +#fullscreen-cover-overlay .fullscreen-top-actions #toggle-fullscreen-lyrics-btn.active { + color: rgb(255 255 255 / 0.96); + background: rgb(255 255 255 / 0.12); +} + #fullscreen-cover-overlay .fullscreen-top-actions #close-fullscreen-cover-btn { order: 1; } @@ -10240,7 +10301,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { #fullscreen-cover-overlay #toggle-ui-btn { top: 1.25rem; - left: calc(80px + 2.3rem + env(safe-area-inset-left)); + left: calc(9.9rem + env(safe-area-inset-left)); right: auto; width: 40px; height: 40px; @@ -10285,7 +10346,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { } #fullscreen-cover-overlay .fullscreen-buttons button.active { - color: rgb(var(--highlight-rgb) / 0.98); + color: rgb(var(--fs-accent-rgb) / 0.98); } #fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn { @@ -10326,7 +10387,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { } #fullscreen-cover-overlay .fs-visualizer-btn.active { - color: rgb(var(--highlight-rgb) / 0.96); + color: rgb(var(--fs-accent-rgb) / 0.96); } #fullscreen-cover-overlay .fs-volume-btn { @@ -10372,7 +10433,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { #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); + background: rgb(var(--fs-accent-rgb) / 0.94); } #fullscreen-cover-overlay .fullscreen-progress-container .progress-bar:hover .progress-fill::after, @@ -10389,6 +10450,13 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { align-items: stretch; justify-content: flex-start; overflow: hidden; + min-width: 0; + opacity: 1; + transform: translateX(0); + transition: + opacity 0.24s ease, + transform 0.34s cubic-bezier(0.22, 1, 0.36, 1), + visibility 0s linear 0s; } #fullscreen-cover-overlay .fullscreen-lyrics-shell, @@ -10404,14 +10472,14 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { #fullscreen-cover-overlay .fullscreen-lyrics-shell { width: min(860px, 100%); min-height: 0; - margin-left: clamp(4rem, 8vw, 8rem); + margin-left: 0; } #fullscreen-cover-overlay .fullscreen-lyrics-content { min-height: 0; height: 100%; position: relative; - padding-left: clamp(2.5rem, 4vw, 4rem); + padding-left: clamp(0.5rem, 1.6vw, 1.5rem); mask-image: none; overflow: visible; scrollbar-width: none; @@ -10459,6 +10527,34 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { opacity: 0.55; } +#fullscreen-cover-overlay.lyrics-hidden .fullscreen-main-view { + --fs-media-column-size: minmax(420px, 760px); + --fs-lyrics-column-size: minmax(0, 0fr); + width: min(760px, 100%); + gap: 0; +} + +#fullscreen-cover-overlay.lyrics-hidden .fullscreen-media-column { + justify-self: center; + width: min(520px, 100%); + transform: translateX(clamp(2rem, 4vw, 3.5rem)); +} + +#fullscreen-cover-overlay.lyrics-hidden .fullscreen-lyrics-pane { + opacity: 0; + transform: translateX(2rem); + visibility: hidden; + pointer-events: none; +} + +@media (prefers-reduced-motion: reduce) { + #fullscreen-cover-overlay .fullscreen-main-view, + #fullscreen-cover-overlay .fullscreen-media-column, + #fullscreen-cover-overlay .fullscreen-lyrics-pane { + transition: none !important; + } +} + #fullscreen-cover-overlay.queue-panel-active .fullscreen-main-view { grid-template-columns: 1fr; width: min(760px, 100%); @@ -10475,110 +10571,217 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { @media (max-width: 980px) { #fullscreen-cover-overlay .fullscreen-main-view { - grid-template-columns: 1fr; + grid-template-columns: minmax(0, 1fr); + grid-template-rows: auto minmax(0, 1fr) auto; width: min(760px, 100%); - gap: 1rem; - align-items: start; + gap: 1.25rem; + align-items: stretch; padding: - calc(4.5rem + env(safe-area-inset-top)) - clamp(1rem, 4vw, 1.5rem) + calc(5rem + env(safe-area-inset-top)) + clamp(1rem, 4vw, 1.75rem) calc(1.5rem + env(safe-area-inset-bottom)) - clamp(1rem, 4vw, 1.5rem); + clamp(1rem, 4vw, 1.75rem); } #fullscreen-cover-overlay .fullscreen-media-column { justify-self: center; transform: none; + width: min(100%, 620px); } #fullscreen-cover-overlay .fullscreen-lyrics-pane { - display: none; + display: flex; + width: min(100%, 620px); + justify-self: center; + min-height: min(48vh, 440px); + } + + #fullscreen-cover-overlay .fullscreen-lyrics-shell { + width: 100%; + margin-left: 0; + } + + #fullscreen-cover-overlay .fullscreen-lyrics-content { + padding-left: 0; } } @media (max-width: 768px) { #fullscreen-cover-overlay { - --fs-mobile-top-btn-size: 44px; + --fs-mobile-top-btn-size: 42px; --fs-mobile-top-btn-gap: 0.6rem; --fs-mobile-top-btn-left: calc(1rem + env(safe-area-inset-left)); } #fullscreen-cover-overlay .fullscreen-cover-content { - padding: 0.75rem 0.75rem calc(0.75rem + env(safe-area-inset-bottom)); + padding: 0 calc(0.9rem + env(safe-area-inset-right)) calc(0.9rem + env(safe-area-inset-bottom)) + calc(0.9rem + env(safe-area-inset-left)); + } + + #fullscreen-dismiss-handle { + display: block; } #fullscreen-cover-overlay .fullscreen-top-actions { - top: calc(0.75rem + env(safe-area-inset-top)); - left: var(--fs-mobile-top-btn-left); - gap: var(--fs-mobile-top-btn-gap); + display: none; } - #fullscreen-cover-overlay .fullscreen-top-actions button, + #fullscreen-cover-overlay .fullscreen-lyrics-toggle, #fullscreen-cover-overlay #toggle-ui-btn { - width: var(--fs-mobile-top-btn-size); - height: var(--fs-mobile-top-btn-size); - background: rgb(9 12 18 / 0.5); - } - - #fullscreen-cover-overlay #toggle-ui-btn { - top: calc(0.75rem + env(safe-area-inset-top)); - left: calc( - var(--fs-mobile-top-btn-left) + - (var(--fs-mobile-top-btn-size) * 2) + - (var(--fs-mobile-top-btn-gap) * 2) - ); + display: none !important; } #fullscreen-cover-overlay .fullscreen-main-view { width: 100%; - gap: 0.85rem; + height: 100%; + grid-template-columns: minmax(78px, 92px) minmax(0, 1fr); + grid-template-rows: auto minmax(0, 1fr) auto; + grid-template-areas: + 'art info' + 'lyrics lyrics' + 'controls controls'; + gap: 1rem 0.9rem; padding: - calc(7.25rem + env(safe-area-inset-top)) - 0.75rem - calc(1.5rem + env(safe-area-inset-bottom)) - 0.75rem; + calc(4.45rem + env(safe-area-inset-top)) + 0 + calc(0.8rem + env(safe-area-inset-bottom)) + 0; } - #fullscreen-cover-overlay .fullscreen-track-info, - #fullscreen-cover-overlay .fullscreen-controls, #fullscreen-cover-overlay .fullscreen-media-column { - width: min(100%, 460px); + display: contents; + width: auto; + } + + #fullscreen-cover-overlay .fullscreen-artwork-card { + grid-area: art; + width: 100%; + max-width: 92px; + border-radius: 12px; + align-self: start; + margin-left: 0.95rem; + box-shadow: 0 20px 48px rgb(0 0 0 / 0.34); + } + + #fullscreen-cover-overlay #fullscreen-cover-image { + border-radius: 12px; + } + + #fullscreen-cover-overlay .fullscreen-track-info { + grid-area: info; + width: 100%; + min-width: 0; + align-self: center; + display: grid; + gap: 0.3rem; + padding-top: 0.2rem; + padding-left: 0.95rem; + } + + #fullscreen-cover-overlay .fullscreen-track-text { + display: grid; + gap: 0.14rem; + } + + #fullscreen-cover-overlay #fullscreen-track-title { + font-size: clamp(1.1rem, 4.7vw, 1.34rem); + line-height: 1.04; + } + + #fullscreen-cover-overlay #fullscreen-track-artist { + margin-top: 0; + font-size: 0.92rem; + color: rgb(255 255 255 / 0.7); } #fullscreen-cover-overlay .fullscreen-actions { + display: none !important; + } + + #fullscreen-cover-overlay #fullscreen-next-track { + display: none !important; + } + + #fullscreen-cover-overlay .fullscreen-lyrics-pane { + grid-area: lyrics; width: 100%; - flex-wrap: wrap; - gap: 0.45rem; + min-height: 0; + justify-self: stretch; } - #fullscreen-cover-overlay .fullscreen-actions .btn-icon { - background: rgb(255 255 255 / 0.06); + #fullscreen-cover-overlay .fullscreen-lyrics-shell { + min-height: 0; + position: relative; + background: transparent !important; + box-shadow: none !important; + border-radius: 0; + overflow: visible; } - #fullscreen-cover-overlay .fullscreen-progress-container { - gap: 0.65rem; + #fullscreen-cover-overlay .fullscreen-lyrics-content { + height: 100%; + padding: 0 0 0.2rem; + overflow: hidden; + mask-image: linear-gradient(180deg, transparent 0%, black 10%, black 88%, transparent 100%); } - #fullscreen-cover-overlay .fullscreen-buttons { - gap: 0.2rem; + #fullscreen-cover-overlay .fullscreen-lyrics-content am-lyrics { + --lyrics-scroll-padding-top: 18%; + --lyplus-font-size-base: clamp(1.75rem, 7vw, 2.35rem); + --lyplus-padding-line: 6px; + --lyplus-text-color: rgba(246, 244, 239, 0.16); + --lyplus-blur-amount: 0.16em; + --lyplus-blur-amount-near: 0.08em; + line-height: 1.2; } - #fullscreen-cover-overlay .fullscreen-buttons button { - width: 38px; - height: 38px; + #fullscreen-cover-overlay .fullscreen-lyrics-empty, + #fullscreen-cover-overlay .fullscreen-lyrics-content .lyrics-loading, + #fullscreen-cover-overlay .fullscreen-lyrics-content .lyrics-error { + padding: 2.5rem 1.2rem 0; } - #fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn { - width: 52px; - height: 52px; + #fullscreen-cover-overlay .fullscreen-controls { + grid-area: controls; + width: 100%; + max-width: none; + margin-top: 0; + padding: 0.15rem 0 0; + gap: 0.9rem; } #fullscreen-cover-overlay .fullscreen-volume-container { - width: min(220px, calc(100% - 2.75rem)); + display: none; + } + + #fullscreen-cover-overlay .fullscreen-progress-container { + gap: 0.55rem; + font-size: 0.72rem; + } + + #fullscreen-cover-overlay .fullscreen-buttons { + gap: 0.1rem; + justify-content: space-between; + } + + #fullscreen-cover-overlay .fullscreen-buttons button { + width: 42px; + height: 42px; + } + + #fullscreen-cover-overlay .fullscreen-buttons #fs-play-pause-btn { + width: 62px; + height: 62px; + box-shadow: 0 14px 28px rgb(0 0 0 / 0.3); + } + + #fullscreen-cover-overlay .fullscreen-volume-container { + width: min(280px, calc(100% - 3rem)); + margin-top: 0; } #fullscreen-cover-overlay .fs-volume-btn { - left: -2.25rem; + left: -2.5rem; } #fullscreen-cover-overlay .fs-volume-bar {