From 0255bffdd1486107a0ce8fe255f0d125c1037041 Mon Sep 17 00:00:00 2001 From: Eduard Prigoana Date: Thu, 26 Mar 2026 20:02:38 +0200 Subject: [PATCH] fix mobile UI + MAYBE direct tidal.com querying for pages functions??? --- .claude/settings.local.json | 9 ++ functions/album/[id].js | 76 ++++++++-- functions/artist/[id].js | 73 ++++++++- functions/playlist/[id].js | 74 ++++++++-- functions/track/[id].js | 86 +++++++++-- package-lock.json | 286 +++--------------------------------- styles.css | 16 ++ 7 files changed, 320 insertions(+), 300 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..4be2f7b --- /dev/null +++ b/.claude/settings.local.json @@ -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:*)" + ] + } +} diff --git a/functions/album/[id].js b/functions/album/[id].js index 0728f66..5f73a01 100644 --- a/functions/album/[id].js +++ b/functions/album/[id].js @@ -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} - + @@ -131,7 +189,7 @@ export async function onRequest(context) { - + @@ -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); } } diff --git a/functions/artist/[id].js b/functions/artist/[id].js index 4ff1e04..bef8df8 100644 --- a/functions/artist/[id].js +++ b/functions/artist/[id].js @@ -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) { ${name} - + - + @@ -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); } } diff --git a/functions/playlist/[id].js b/functions/playlist/[id].js index a535a20..f254bf5 100644 --- a/functions/playlist/[id].js +++ b/functions/playlist/[id].js @@ -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} - + @@ -128,7 +184,7 @@ export async function onRequest(context) { - + @@ -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); } } diff --git a/functions/track/[id].js b/functions/track/[id].js index 20dc691..bf2205d 100644 --- a/functions/track/[id].js +++ b/functions/track/[id].js @@ -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 ? ` @@ -153,7 +221,7 @@ export async function onRequest(context) { ${title} by ${artist} - + @@ -162,9 +230,9 @@ export async function onRequest(context) { - + ${audioMeta} - + @@ -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); } } diff --git a/package-lock.json b/package-lock.json index 73404bf..4356253 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/styles.css b/styles.css index 69f77dc..28486da 100644 --- a/styles.css +++ b/styles.css @@ -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),