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 {