fix mobile UI + MAYBE direct tidal.com querying for pages functions???

This commit is contained in:
Eduard Prigoana 2026-03-26 20:02:38 +02:00
parent fdb13d4a1c
commit 0255bffdd1
7 changed files with 320 additions and 300 deletions

View file

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(grep:*)",
"Bash(find /c/Users/Admin/Documents/GitHub/monochrome -type f \\\\\\(-name *.html -o -name index.html \\\\\\))",
"Bash(xargs wc:*)"
]
}
}

View file

@ -1,5 +1,50 @@
// functions/album/[id].js
class TidalAPI {
static CLIENT_ID = 'txNoH4kkV41MfH25';
static CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
async getToken() {
const params = new URLSearchParams({
client_id: TidalAPI.CLIENT_ID,
client_secret: TidalAPI.CLIENT_SECRET,
grant_type: 'client_credentials',
});
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + btoa(`${TidalAPI.CLIENT_ID}:${TidalAPI.CLIENT_SECRET}`),
},
body: params,
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
return data.access_token;
}
async fetchJson(url, params = {}) {
const token = await this.getToken();
const u = new URL(url);
Object.entries(params).forEach(([k, v]) => u.searchParams.set(k, String(v)));
const res = await fetch(u.toString(), {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`Tidal API error: ${res.status}`);
return res.json();
}
async getAlbumMetadata(id) {
return await this.fetchJson(`https://api.tidal.com/v1/albums/${id}`, { countryCode: 'US' });
}
getCoverUrl(id, size = '1280') {
if (!id) return '';
const formattedId = String(id).replace(/-/g, '/');
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
}
}
class ServerAPI {
constructor() {
this.INSTANCES_URLS = [
@ -96,13 +141,26 @@ export async function onRequest(context) {
const albumId = params.id;
if (isBot && albumId) {
let api;
let album;
let tracks = [];
try {
const api = new ServerAPI();
const data = await api.getAlbumMetadata(albumId);
const album = data.data || data.album || data;
const tracks = album.items || data.tracks || [];
api = new TidalAPI();
album = await api.getAlbumMetadata(albumId);
} catch (directError) {
console.warn(`Direct Tidal API failed for album ${albumId}, falling back to proxies:`, directError);
try {
api = new ServerAPI();
const data = await api.getAlbumMetadata(albumId);
album = data.data || data.album || data;
tracks = album.items || data.tracks || [];
} catch (fallbackError) {
console.error(`All methods failed for album ${albumId}:`, fallbackError);
}
}
if (album && (album.title || album.name)) {
if (album && (album.title || album.name)) {
try {
const title = album.title || album.name;
const artist = album.artist?.name || 'Unknown Artist';
const year = album.releaseDate ? new Date(album.releaseDate).getFullYear() : '';
@ -122,7 +180,7 @@ export async function onRequest(context) {
<title>${title}</title>
<meta name="description" content="${description}">
<meta name="theme-color" content="#000000">
<meta property="og:site_name" content="Monochrome">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
@ -131,7 +189,7 @@ export async function onRequest(context) {
<meta property="og:url" content="${pageUrl}">
<meta property="music:musician" content="${artist}">
<meta property="music:release_date" content="${album.releaseDate}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
@ -146,9 +204,9 @@ export async function onRequest(context) {
`;
return new Response(metaHtml, { headers: { 'content-type': 'text/html;charset=UTF-8' } });
} catch (error) {
console.error(`Error generating meta tags for album ${albumId}:`, error);
}
} catch (error) {
console.error(`Error for album ${albumId}:`, error);
}
}

View file

@ -1,5 +1,50 @@
// functions/artist/[id].js
class TidalAPI {
static CLIENT_ID = 'txNoH4kkV41MfH25';
static CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
async getToken() {
const params = new URLSearchParams({
client_id: TidalAPI.CLIENT_ID,
client_secret: TidalAPI.CLIENT_SECRET,
grant_type: 'client_credentials',
});
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + btoa(`${TidalAPI.CLIENT_ID}:${TidalAPI.CLIENT_SECRET}`),
},
body: params,
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
return data.access_token;
}
async fetchJson(url, params = {}) {
const token = await this.getToken();
const u = new URL(url);
Object.entries(params).forEach(([k, v]) => u.searchParams.set(k, String(v)));
const res = await fetch(u.toString(), {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`Tidal API error: ${res.status}`);
return res.json();
}
async getArtistMetadata(id) {
return await this.fetchJson(`https://api.tidal.com/v1/artists/${id}`, { countryCode: 'US' });
}
getArtistPictureUrl(id, size = '750') {
if (!id) return '';
const formattedId = id.replace(/-/g, '/');
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
}
}
class ServerAPI {
constructor() {
this.INSTANCES_URLS = [
@ -96,12 +141,24 @@ export async function onRequest(context) {
const artistId = params.id;
if (isBot && artistId) {
let api;
let artist;
try {
const api = new ServerAPI();
const data = await api.getArtistMetadata(artistId);
const artist = data.artist || data.data || data;
api = new TidalAPI();
artist = await api.getArtistMetadata(artistId);
} catch (directError) {
console.warn(`Direct Tidal API failed for artist ${artistId}, falling back to proxies:`, directError);
try {
api = new ServerAPI();
const data = await api.getArtistMetadata(artistId);
artist = data.artist || data.data || data;
} catch (fallbackError) {
console.error(`All methods failed for artist ${artistId}:`, fallbackError);
}
}
if (artist && (artist.name || artist.title)) {
if (artist && (artist.name || artist.title)) {
try {
const name = artist.name || artist.title;
const description = `Listen to ${name} on Monochrome`;
const imageUrl = artist.picture
@ -117,14 +174,14 @@ export async function onRequest(context) {
<title>${name}</title>
<meta name="description" content="${description}">
<meta name="theme-color" content="#000000">
<meta property="og:site_name" content="Monochrome">
<meta property="og:title" content="${name}">
<meta property="og:description" content="${description}">
<meta property="og:image" content="${imageUrl}">
<meta property="og:type" content="profile">
<meta property="og:url" content="${pageUrl}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${name}">
<meta name="twitter:description" content="${description}">
@ -138,9 +195,9 @@ export async function onRequest(context) {
`;
return new Response(metaHtml, { headers: { 'content-type': 'text/html;charset=UTF-8' } });
} catch (error) {
console.error(`Error generating meta tags for artist ${artistId}:`, error);
}
} catch (error) {
console.error(`Error for artist ${artistId}:`, error);
}
}

View file

@ -1,5 +1,50 @@
// functions/playlist/[id].js
class TidalAPI {
static CLIENT_ID = 'txNoH4kkV41MfH25';
static CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
async getToken() {
const params = new URLSearchParams({
client_id: TidalAPI.CLIENT_ID,
client_secret: TidalAPI.CLIENT_SECRET,
grant_type: 'client_credentials',
});
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + btoa(`${TidalAPI.CLIENT_ID}:${TidalAPI.CLIENT_SECRET}`),
},
body: params,
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
return data.access_token;
}
async fetchJson(url, params = {}) {
const token = await this.getToken();
const u = new URL(url);
Object.entries(params).forEach(([k, v]) => u.searchParams.set(k, String(v)));
const res = await fetch(u.toString(), {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`Tidal API error: ${res.status}`);
return res.json();
}
async getPlaylistMetadata(id) {
return await this.fetchJson(`https://api.tidal.com/v1/playlists/${id}`, { countryCode: 'US' });
}
getCoverUrl(id, size = '1080') {
if (!id) return '';
const formattedId = String(id).replace(/-/g, '/');
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
}
}
class ServerAPI {
constructor() {
this.INSTANCES_URLS = [
@ -75,7 +120,6 @@ class ServerAPI {
const response = await this.fetchWithRetry(`/playlist/${id}`);
return await response.json();
} catch {
// Fallback to query param style
const response = await this.fetchWithRetry(`/playlist?id=${id}`);
return await response.json();
}
@ -97,12 +141,24 @@ export async function onRequest(context) {
const playlistId = params.id;
if (isBot && playlistId) {
let api;
let playlist;
try {
const api = new ServerAPI();
const data = await api.getPlaylistMetadata(playlistId);
const playlist = data.playlist || data.data || data;
api = new TidalAPI();
playlist = await api.getPlaylistMetadata(playlistId);
} catch (directError) {
console.warn(`Direct Tidal API failed for playlist ${playlistId}, falling back to proxies:`, directError);
try {
api = new ServerAPI();
const data = await api.getPlaylistMetadata(playlistId);
playlist = data.playlist || data.data || data;
} catch (fallbackError) {
console.error(`All methods failed for playlist ${playlistId}:`, fallbackError);
}
}
if (playlist && (playlist.title || playlist.name)) {
if (playlist && (playlist.title || playlist.name)) {
try {
const title = playlist.title || playlist.name;
const trackCount = playlist.numberOfTracks;
const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`;
@ -120,7 +176,7 @@ export async function onRequest(context) {
<title>${title}</title>
<meta name="description" content="${description}">
<meta name="theme-color" content="#000000">
<meta property="og:site_name" content="Monochrome">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
@ -128,7 +184,7 @@ export async function onRequest(context) {
<meta property="og:type" content="music.playlist">
<meta property="og:url" content="${pageUrl}">
<meta property="music:song_count" content="${trackCount}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
@ -143,9 +199,9 @@ export async function onRequest(context) {
`;
return new Response(metaHtml, { headers: { 'content-type': 'text/html;charset=UTF-8' } });
} catch (error) {
console.error(`Error generating meta tags for playlist ${playlistId}:`, error);
}
} catch (error) {
console.error(`Error for playlist ${playlistId}:`, error);
}
}

View file

@ -12,6 +12,61 @@ function getTrackArtists(track = {}, { fallback = 'Unknown Artist' } = {}) {
return fallback;
}
class TidalAPI {
static CLIENT_ID = 'txNoH4kkV41MfH25';
static CLIENT_SECRET = 'dQjy0MinCEvxi1O4UmxvxWnDjt4cgHBPw8ll6nYBk98=';
async getToken() {
const params = new URLSearchParams({
client_id: TidalAPI.CLIENT_ID,
client_secret: TidalAPI.CLIENT_SECRET,
grant_type: 'client_credentials',
});
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Basic ' + btoa(`${TidalAPI.CLIENT_ID}:${TidalAPI.CLIENT_SECRET}`),
},
body: params,
});
if (!res.ok) throw new Error(`Token request failed: ${res.status}`);
const data = await res.json();
return data.access_token;
}
async fetchJson(url, params = {}) {
const token = await this.getToken();
const u = new URL(url);
Object.entries(params).forEach(([k, v]) => u.searchParams.set(k, String(v)));
const res = await fetch(u.toString(), {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`Tidal API error: ${res.status}`);
return res.json();
}
async getTrackMetadata(id) {
return await this.fetchJson(`https://api.tidal.com/v1/tracks/${id}/`, { countryCode: 'US' });
}
async getStreamUrl(id) {
const data = await this.fetchJson(`https://api.tidal.com/v1/tracks/${id}/playbackinfo`, {
audioquality: 'LOW',
playbackmode: 'STREAM',
assetpresentation: 'FULL',
countryCode: 'US',
});
return data.url || data.streamUrl;
}
getCoverUrl(id, size = '1280') {
if (!id) return '';
const formattedId = String(id).replace(/-/g, '/');
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
}
}
class ServerAPI {
constructor() {
this.INSTANCES_URLS = [
@ -116,11 +171,24 @@ export async function onRequest(context) {
const trackId = params.id;
if (isBot && trackId) {
// Try direct Tidal API first, fall back to proxy instances
let api;
let track;
try {
const api = new ServerAPI();
const track = await api.getTrackMetadata(trackId);
api = new TidalAPI();
track = await api.getTrackMetadata(trackId);
} catch (directError) {
console.warn(`Direct Tidal API failed for track ${trackId}, falling back to proxies:`, directError);
try {
api = new ServerAPI();
track = await api.getTrackMetadata(trackId);
} catch (fallbackError) {
console.error(`All methods failed for track ${trackId}:`, fallbackError);
}
}
if (track) {
if (track) {
try {
const title = getTrackTitle(track);
const artist = getTrackArtists(track);
const description = `${artist} - ${track.album.title}`;
@ -136,7 +204,7 @@ export async function onRequest(context) {
console.error('Failed to fetch stream fallback:', e);
}
}
// this prob wont work im js winging it
const audioMeta = audioUrl
? `
<meta property="og:audio" content="${audioUrl}">
@ -153,7 +221,7 @@ export async function onRequest(context) {
<meta charset="UTF-8">
<title>${title} by ${artist}</title>
<meta name="description" content="${description}">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:image" content="${imageUrl}">
@ -162,9 +230,9 @@ export async function onRequest(context) {
<meta property="music:duration" content="${track.duration}">
<meta property="music:album" content="${track.album.title}">
<meta property="music:musician" content="${artist}">
${audioMeta}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
@ -182,9 +250,9 @@ export async function onRequest(context) {
return new Response(metaHtml, {
headers: { 'content-type': 'text/html;charset=UTF-8' },
});
} catch (error) {
console.error(`Error generating meta tags for track ${trackId}:`, error);
}
} catch (error) {
console.error(`Error generating meta tags for track ${trackId}:`, error);
}
}

286
package-lock.json generated
View file

@ -27,8 +27,8 @@
"butterchurn-presets": "^2.4.7",
"client-zip": "^2.5.0",
"cookie-session": "^2.1.1",
"dashjs": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"eventemitter3": "^5.0.4",
"events": "^3.3.0",
"fuse.js": "^7.1.0",
"hls.js": "^1.6.15",
"jose": "^6.2.0",
@ -36,6 +36,7 @@
"mime": "^4.1.0",
"npm": "^11.11.1",
"pocketbase": "^0.26.8",
"shaka-player": "^5.0.7",
"simple-icons": "^16.12.0",
"svgo": "^4.0.1",
"url-toolkit": "^2.2.5",
@ -4412,110 +4413,6 @@
"string.prototype.matchall": "^4.0.6"
}
},
"node_modules/@svta/cml-608": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
}
},
"node_modules/@svta/cml-cmcd": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-cta": "1.0.1",
"@svta/cml-structured-field-values": "1.0.1",
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-cmsd": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-cta": "1.0.1",
"@svta/cml-structured-field-values": "1.0.1",
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-cta": {
"version": "1.0.1",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-structured-field-values": "1.0.1",
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-dash": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-id3": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-request": {
"version": "1.0.1",
"license": "Apache-2.0",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-utils": "1.0.1",
"@svta/cml-xml": "1.0.1"
}
},
"node_modules/@svta/cml-structured-field-values": {
"version": "1.0.1",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/cml-utils": {
"version": "1.0.1",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=20"
}
},
"node_modules/@svta/cml-xml": {
"version": "1.0.1",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@svta/cml-utils": "1.0.1"
}
},
"node_modules/@svta/common-media-library": {
"version": "0.18.1",
"license": "Apache-2.0",
@ -5264,39 +5161,6 @@
"node": ">=6.0.0"
}
},
"node_modules/bcp-47": {
"version": "2.1.0",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^2.0.0",
"is-alphanumerical": "^2.0.0",
"is-decimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/bcp-47-match": {
"version": "2.0.3",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/bcp-47-normalize": {
"version": "2.3.0",
"license": "MIT",
"dependencies": {
"bcp-47": "^2.0.0",
"bcp-47-match": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
@ -5667,10 +5531,6 @@
"node": ">=12"
}
},
"node_modules/codem-isoboxer": {
"version": "0.3.10",
"license": "MIT"
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@ -6646,30 +6506,6 @@
"node": ">=8"
}
},
"node_modules/dashjs": {
"version": "5.1.1",
"resolved": "https://github.com/Dash-Industry-Forum/dash.js/archive/refs/tags/v5.1.1.tar.gz",
"integrity": "sha512-lhD1tvEe4PO6t086flm6WfO2Jt1EOIolDQ17F3vLomMthaL1RH96h8peIQTvrDvfSJTRXeisL+CwPj4oud5e9g==",
"license": "BSD-3-Clause",
"dependencies": {
"@svta/cml-608": "1.0.1",
"@svta/cml-cmcd": "1.0.1",
"@svta/cml-cmsd": "1.0.1",
"@svta/cml-dash": "1.0.1",
"@svta/cml-id3": "1.0.1",
"@svta/cml-request": "1.0.1",
"@svta/cml-xml": "1.0.1",
"bcp-47-match": "^2.0.3",
"bcp-47-normalize": "^2.3.0",
"codem-isoboxer": "0.3.10",
"fast-deep-equal": "3.1.3",
"html-entities": "^2.5.2",
"imsc": "^1.1.5",
"localforage": "^1.10.0",
"path-browserify": "^1.0.1",
"ua-parser-js": "^1.0.37"
}
},
"node_modules/data-view-buffer": {
"version": "1.0.2",
"dev": true,
@ -7569,6 +7405,15 @@
"version": "5.0.4",
"license": "MIT"
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
@ -7599,6 +7444,7 @@
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"dev": true,
"license": "MIT"
},
"node_modules/fast-fifo": {
@ -8992,20 +8838,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/html-entities": {
"version": "2.6.0",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/mdevils"
},
{
"type": "patreon",
"url": "https://patreon.com/mdevils"
}
],
"license": "MIT"
},
"node_modules/html-tags": {
"version": "3.3.1",
"dev": true,
@ -9084,10 +8916,6 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"dev": true,
@ -9111,17 +8939,6 @@
"node": ">=4"
}
},
"node_modules/imsc": {
"version": "1.1.5",
"license": "BSD-2-Clause",
"dependencies": {
"sax": "1.2.1"
}
},
"node_modules/imsc/node_modules/sax": {
"version": "1.2.1",
"license": "ISC"
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"dev": true,
@ -9180,26 +8997,6 @@
"node": ">=8"
}
},
"node_modules/is-alphabetical": {
"version": "2.0.1",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-alphanumerical": {
"version": "2.0.1",
"license": "MIT",
"dependencies": {
"is-alphabetical": "^2.0.0",
"is-decimal": "^2.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"dev": true,
@ -9324,14 +9121,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-decimal": {
"version": "2.0.1",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
@ -9960,13 +9749,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.1.1",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"dev": true,
@ -10037,13 +9819,6 @@
"node": ">=4"
}
},
"node_modules/localforage": {
"version": "1.10.0",
"license": "Apache-2.0",
"dependencies": {
"lie": "3.1.1"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"dev": true,
@ -12635,10 +12410,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"license": "MIT"
},
"node_modules/path-exists": {
"version": "4.0.0",
"dev": true,
@ -14027,6 +13798,15 @@
"node": ">=0.10.0"
}
},
"node_modules/shaka-player": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/shaka-player/-/shaka-player-5.0.8.tgz",
"integrity": "sha512-f886rKRvQ0IKhWGk+rINS++YTjTJyc4DT5YypTsHW6wiNV9fiHi2n35+lg5R+hj9RfhqkmJHMjJb3gprUTNa8w==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/sharp": {
"version": "0.34.5",
"dev": true,
@ -15342,30 +15122,6 @@
"node": ">=14.17"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.41",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"license": "MIT",
"bin": {
"ua-parser-js": "script/cli.js"
},
"engines": {
"node": "*"
}
},
"node_modules/uglify-js": {
"version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",

View file

@ -1328,6 +1328,8 @@ ul {
.search-bar {
width: 80%;
max-width: 100%;
min-width: 0;
flex: 1;
}
.search-bar input {
@ -1634,6 +1636,13 @@ input[type='search']::-webkit-search-cancel-button {
gap: var(--spacing-xs);
margin-bottom: var(--spacing-lg);
border-bottom: 1px solid var(--border);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.search-tabs::-webkit-scrollbar {
display: none;
}
.search-tab {
@ -1645,6 +1654,8 @@ input[type='search']::-webkit-search-cancel-button {
font-size: 1rem;
font-weight: 500;
border-bottom: 2px solid transparent;
white-space: nowrap;
flex-shrink: 0;
/* Keep layout stable */
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
@ -6300,6 +6311,8 @@ img[src=''] {
padding: var(--spacing-md);
grid-area: main;
overflow-y: visible;
overflow-x: hidden;
min-width: 0;
}
.main-header {
@ -6335,6 +6348,7 @@ img[src=''] {
.search-bar {
max-width: none;
width: auto;
}
.content-section {
@ -6960,6 +6974,8 @@ img[src=''] {
.main-content {
padding: var(--spacing-sm);
grid-area: main;
overflow-x: hidden;
min-width: 0;
}
.progress-bar:not(.waveform-loaded),