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),