feat: refine fullscreen apple player
This commit is contained in:
parent
e00368597d
commit
2573870004
11 changed files with 571 additions and 453 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -138,13 +138,14 @@
|
|||
z-index: 0;
|
||||
"
|
||||
></div>
|
||||
<button id="fullscreen-dismiss-handle" type="button" aria-label="Dismiss fullscreen"></button>
|
||||
<button id="toggle-ui-btn" class="fullscreen-ui-toggle" title="Toggle UI">
|
||||
<use svg="!lucide/eye-off.svg" size="24" />
|
||||
</button>
|
||||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Toggle Lyrics">
|
||||
<use svg="!lucide/mic-vocal.svg" size="24" />
|
||||
</button>
|
||||
<div class="fullscreen-top-actions">
|
||||
<button id="toggle-fullscreen-lyrics-btn" class="fullscreen-lyrics-toggle" title="Hide Lyrics">
|
||||
<use svg="!lucide/mic-vocal.svg" size="20" />
|
||||
</button>
|
||||
<button id="fs-visualizer-btn" class="fs-visualizer-btn" title="Disable Visualizer">
|
||||
<use svg="!lucide/audio-lines.svg" size="20" />
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
177
js/storage.js
177
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) {
|
||||
|
|
|
|||
260
js/ui.js
260
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 = '<div class="fullscreen-lyrics-empty">Lyrics are not available for this track.</div>';
|
||||
}
|
||||
}
|
||||
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 = '<div class="fullscreen-lyrics-empty">Lyrics appear here.</div>';
|
||||
}
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
363
styles.css
363
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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue