From c73572e195cd025c45846eba9afda9f6b5172943 Mon Sep 17 00:00:00 2001 From: edideaur Date: Sat, 4 Apr 2026 21:41:37 +0000 Subject: [PATCH 01/11] lint: fix JS errors and duplicate CSS selectors --- js/app.js | 2 +- js/player.js | 55 +++++++++++++++++++++++++++++++++++----------------- styles.css | 21 +++++++------------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/js/app.js b/js/app.js index 93022c1..1e5b799 100644 --- a/js/app.js +++ b/js/app.js @@ -500,7 +500,7 @@ document.addEventListener('DOMContentLoaded', async () => { // Initialize tracker initTracker().catch(console.error); - fetchcontributors(); + await fetchcontributors(); const castBtn = document.getElementById('cast-btn'); initializeCasting(audioPlayer, castBtn); diff --git a/js/player.js b/js/player.js index f39bc67..5b779a0 100644 --- a/js/player.js +++ b/js/player.js @@ -487,7 +487,7 @@ export class Player { const timeRemaining = duration - currentTime; // Preload if we are in last 30 seconds of song - const shouldPreload = (duration > 0 && timeRemaining <= 30); + const shouldPreload = duration > 0 && timeRemaining <= 30; if (shouldPreload) { this._pendingPreload = false; @@ -537,7 +537,7 @@ export class Player { albumPeakAmplitude: trackData.info.albumPeakAmplitude, }; } - } catch(e) {} // Fail silently + } catch (_e) {} // Fail silently } this.preloadCache.set(track.id, streamInfo); @@ -546,30 +546,45 @@ export class Player { // Warm connection and pre-fetch if (!streamUrl.startsWith('blob:')) { if (streamUrl.includes('.mpd') || streamUrl.includes('.m3u8')) { - if (this.shakaInitialized && this.shakaPlayer && typeof this.shakaPlayer.preload === 'function') { + if ( + this.shakaInitialized && + this.shakaPlayer && + typeof this.shakaPlayer.preload === 'function' + ) { try { let preloadConfig = undefined; if (typeof this.shakaPlayer.getConfiguration === 'function') { preloadConfig = this.shakaPlayer.getConfiguration(); - const stats = typeof this.shakaPlayer.getStats === 'function' ? this.shakaPlayer.getStats() : null; + const stats = + typeof this.shakaPlayer.getStats === 'function' + ? this.shakaPlayer.getStats() + : null; if (stats && stats.estimatedBandwidth) { preloadConfig.abr.defaultBandwidthEstimate = stats.estimatedBandwidth; } - + // Lock the preload to the exact current audio codec to prevent ABR mismatch, // which forces the player to discard and re-fetch chunks on slow connections. preloadConfig.abr.enabled = false; try { - const variants = typeof this.shakaPlayer.getVariantTracks === 'function' ? this.shakaPlayer.getVariantTracks() : []; - const activeVariant = variants.find(v => v.active); + const variants = + typeof this.shakaPlayer.getVariantTracks === 'function' + ? this.shakaPlayer.getVariantTracks() + : []; + const activeVariant = variants.find((v) => v.active); if (activeVariant && activeVariant.audioCodec) { preloadConfig.preferredAudioCodecs = [activeVariant.audioCodec]; } - } catch (e) {} + } catch (_e) {} } - const preloadManager = await this.shakaPlayer.preload(streamUrl, null, null, preloadConfig); + const preloadManager = await this.shakaPlayer.preload( + streamUrl, + null, + null, + preloadConfig + ); streamInfo.preloadManager = preloadManager; - } catch (e) { + } catch (_e) { // Ignore preload errors, will just load fresh } } else { @@ -801,7 +816,7 @@ export class Player { this.hls.destroy(); this.hls = null; } - + // Retain the initialized Shaka player if we are remaining on the same HTMLMediaElement if (this.shakaInitialized && this.shakaPlayer) { if (this.shakaPlayer.getMediaElement() !== activeElement) { @@ -1015,14 +1030,17 @@ export class Player { } else if (streamUrl.startsWith('blob:') || streamUrl.includes('.mpd')) { await this.shakaPlayer.attach(activeElement); - const loadTarget = track.type == 'video' && this.preloadCache.has(track.id) ? - (this.preloadCache.get(track.id).preloadManager || streamUrl) : streamUrl; + const loadTarget = + track.type == 'video' && this.preloadCache.has(track.id) + ? this.preloadCache.get(track.id).preloadManager || streamUrl + : streamUrl; try { await this.shakaPlayer.load(loadTarget); } catch (e) { - console.error("PreloadManager load Error:", e); if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl); - else throw e; + console.error('PreloadManager load Error:', e); + if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl); + else throw e; } this.shakaInitialized = true; @@ -1097,8 +1115,9 @@ export class Player { await this.shakaPlayer.load(loadTarget); } } catch (e) { - console.error("PreloadManager load Error:", e); if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl); - else throw e; + console.error('PreloadManager load Error:', e); + if (loadTarget !== streamUrl) await this.shakaPlayer.load(streamUrl); + else throw e; } this.shakaInitialized = true; @@ -1109,7 +1128,7 @@ export class Player { this.updateAdaptiveQualityBadge(); - // Instantly trigger playback rather than explicitly waiting for 'canplay' + // Instantly trigger playback rather than explicitly waiting for 'canplay' // which delays the event loop and natively adds gap/latency await this.safePlay(activeElement); } else { diff --git a/styles.css b/styles.css index ce49775..2d3d2e4 100644 --- a/styles.css +++ b/styles.css @@ -1763,6 +1763,10 @@ input[type='search']::-webkit-search-cancel-button { .settings-tab-content { display: none; + max-width: 100%; + min-width: 0; + overflow-x: auto; + overflow-y: visible; } .settings-tab-content.active { @@ -1770,14 +1774,6 @@ input[type='search']::-webkit-search-cancel-button { animation: fade-in-slide-up 0.4s var(--ease-out-back); } -/* Prevent settings tab content from overflowing on small displays (fixes #313) */ -.settings-tab-content { - max-width: 100%; - min-width: 0; - overflow-x: auto; - overflow-y: visible; -} - .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); @@ -4301,6 +4297,7 @@ input:checked + .slider::before { justify-content: center; gap: 1rem; margin-top: 1rem; + position: relative; } .fs-volume-btn { @@ -5910,10 +5907,6 @@ img[src=''] { color: white; } -#fullscreen-cover-overlay.is-video-mode .fullscreen-volume-container { - margin-top: 0.5rem; -} - #fullscreen-cover-overlay.ui-hidden .fullscreen-main-view, #fullscreen-cover-overlay.ui-hidden .fullscreen-controls, #fullscreen-cover-overlay.ui-hidden #fullscreen-next-track, @@ -9881,10 +9874,10 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn { .chat-msg { margin-bottom: 0.5rem; - animation: fadeIn 0.2s ease-out; + animation: fade-in 0.2s ease-out; } -@keyframes fadeIn { +@keyframes fade-in { from { opacity: 0; transform: translateY(5px); From 2c9ac1ecb1d04a0600505a907e34cbaf92adf872 Mon Sep 17 00:00:00 2001 From: edideaur Date: Sat, 4 Apr 2026 21:53:05 +0000 Subject: [PATCH 02/11] fix: gracefully handle contributor fetch faliure --- js/app.js | 48 ++++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/js/app.js b/js/app.js index 1e5b799..74db58a 100644 --- a/js/app.js +++ b/js/app.js @@ -127,32 +127,36 @@ async function loadDownloadsModule() { } async function fetchcontributors() { - const response = await fetch('https://api.samidy.com/api/contributors'); - const data1 = await response.json(); + try { + const response = await fetch('https://api.samidy.com/api/contributors'); + if (!response.ok) return; + const data1 = await response.json(); - const data = data1.filter( - (user) => user.type !== 'Bot' && user.login !== 'edidealt' && user.login !== 'satanyahoo' - ); + const data = data1.filter( + (user) => user.type !== 'Bot' && user.login !== 'edidealt' && user.login !== 'satanyahoo' + ); - const edideaur = data.find((user) => user.login === 'edideaur'); - if (edideaur) { - edideaur.contributions += data1.find((u) => u.login === 'edidealt')?.contributions || 0; - edideaur.contributions += data1.find((u) => u.login === 'satanyahoo')?.contributions || 0; - } + const edideaur = data.find((user) => user.login === 'edideaur'); + if (edideaur) { + edideaur.contributions += data1.find((u) => u.login === 'edidealt')?.contributions || 0; + edideaur.contributions += data1.find((u) => u.login === 'satanyahoo')?.contributions || 0; + } - const con = document.querySelector('.about-contributors'); + const con = document.querySelector('.about-contributors'); + if (!con) return; - data.forEach((user) => { - const userDIV = document.createElement('div'); - userDIV.innerHTML = ` - - ${user.login} - ${user.login} - Contributions: ${user.contributions} - - `; - con.appendChild(userDIV); - }); + data.forEach((user) => { + const userDIV = document.createElement('div'); + userDIV.innerHTML = ` + + ${user.login} + ${user.login} + Contributions: ${user.contributions} + + `; + con.appendChild(userDIV); + }); + } catch (e) {} } async function loadMetadataModule() { From d9d6a7d7d118b49ecb117c02f150e95a82abe039 Mon Sep 17 00:00:00 2001 From: edideaur Date: Sat, 4 Apr 2026 22:32:16 +0000 Subject: [PATCH 03/11] fix? lint workflow --- .github/workflows/copilot-setup-steps.yml | 55 +++++++++++------------ 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index e7acda1..ee3d1d8 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -1,39 +1,38 @@ -name: "Copilot Setup Steps" +name: 'Copilot Setup Steps' # Automatically run the setup steps when they are changed to allow for easy validation, and # allow manual testing through the repository's "Actions" tab on: - workflow_dispatch: - push: - paths: - - .github/workflows/copilot-setup-steps.yml - pull_request: - paths: - - .github/workflows/copilot-setup-steps.yml + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml jobs: - # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. - copilot-setup-steps: - runs-on: ubuntu-latest + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest - # Set the permissions to the lowest permissions possible needed for your steps. - # Copilot will be given its own token for its operations. - permissions: - # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. - # If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. - contents: read + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + contents: read + workflows: write - steps: - - name: Checkout code - uses: actions/checkout@v6 + steps: + - name: Checkout code + uses: actions/checkout@v6 - - name: Setup Bun - uses: oven-sh/setup-bun@v1 - with: - bun-version: latest + - name: Setup Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest - - name: Install dependencies - run: bun install + - name: Install dependencies + run: bun install - - name: Install playwright dependencies - run: bun run install:playwright + - name: Install playwright dependencies + run: bun run install:playwright From f1e961d4a94d0a378498cec0b6454748f414876d Mon Sep 17 00:00:00 2001 From: edideaur Date: Sun, 5 Apr 2026 01:05:22 +0000 Subject: [PATCH 04/11] feat: compress editors picks images to webp --- .github/workflows/editors-picks.yml | 20 ++++++-- gen-editors-picks.py | 76 ++++++++++++++++++++++++++--- js/taglib.worker.ts | 2 +- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/.github/workflows/editors-picks.yml b/.github/workflows/editors-picks.yml index 3745ac8..0ce8f3f 100644 --- a/.github/workflows/editors-picks.yml +++ b/.github/workflows/editors-picks.yml @@ -25,7 +25,7 @@ jobs: echo "Files changed in this commit:" echo "$CHANGED" if echo "$CHANGED" | grep -qE '^public/editors-picks\.json$|^public/editors-picks-old/'; then - echo "Detected changes to generated files in this commit — backing off to avoid overwriting manual edits." + echo "Detected changes to generated files in this commit - backing off to avoid overwriting manual edits." echo "skip=true" >> "$GITHUB_OUTPUT" else echo "skip=false" >> "$GITHUB_OUTPUT" @@ -37,6 +37,10 @@ jobs: with: python-version: '3.x' + - name: Install webp + if: steps.backoff.outputs.skip == 'false' + run: sudo apt-get update && sudo apt-get install -y webp + - name: Archive current editors picks if: steps.backoff.outputs.skip == 'false' run: | @@ -60,9 +64,19 @@ jobs: label = m.group(1).strip() break - # Copy current picks to archive + # Copy current picks to archive, replacing monochrome URLs with UUIDs with open("public/editors-picks.json") as f: current = json.load(f) + + # Replace cover URLs with original UUIDs + url_pattern = re.compile(r'^https://monochrome\.tf/editors-picks-images/([a-f0-9-]+)\.webp$') + for item in current: + for field in ['cover', 'picture', 'image']: + if field in item and item[field]: + match = url_pattern.match(item[field]) + if match: + item[field] = match.group(1) + with open(archive_path, "w") as f: json.dump(current, f, indent=4) @@ -89,7 +103,7 @@ jobs: run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" - git add public/editors-picks.json public/editors-picks-old/ + git add public/editors-picks.json public/editors-picks-old/ public/editors-picks-images/ git diff --staged --quiet && echo "No changes to commit." && exit 0 git commit -m "chore: update editors picks" git push diff --git a/gen-editors-picks.py b/gen-editors-picks.py index 95e102e..b2e03f3 100644 --- a/gen-editors-picks.py +++ b/gen-editors-picks.py @@ -7,11 +7,16 @@ import re import sys import hashlib import time +import os +import subprocess +import shutil +import tempfile INPUT_FILE = "editors-picks-input.txt" +IMAGES_DIR = "public/editors-picks-images" COUNTRY = "US" -# Tidal internal token — replace when expired +# Tidal internal token replace when expired TIDAL_TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MjQ2NzQ2LCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.ksUE4yhQ39IG7oHWk8DyJ91dwIoDVWGzvTAnpeDJ5p-_Gp0F_yO858xDO11AINBaahQCq0jlbqWqIaTqCTOjqg" TIDAL_HEADERS = { @@ -83,6 +88,61 @@ def fetch_podcast(feed_id): return podcast_get(f"/podcasts/byfeedid?id={feed_id}&pretty") +# ── Image processing ─────────────────────────────────────────────────────────── + +def clear_images_dir(): + if os.path.exists(IMAGES_DIR): + shutil.rmtree(IMAGES_DIR) + os.makedirs(IMAGES_DIR, exist_ok=True) + + +def is_uuid_cover(cover_value): + if not cover_value: + return False + return bool(re.match(r'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$', cover_value)) + + +def uuid_to_path_segments(uuid): + return uuid.replace('-', '/') + + +def download_and_process_cover(cover_uuid): + url = f"https://resources.tidal.com/images/{uuid_to_path_segments(cover_uuid)}/640x640.jpg" + + with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp: + tmp_path = tmp.name + + try: + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + with urllib.request.urlopen(req) as resp: + with open(tmp_path, 'wb') as f: + shutil.copyfileobj(resp, f) + + output_path = os.path.join(IMAGES_DIR, f"{cover_uuid}.webp") + + subprocess.run( + ['cwebp', '-q', '50', '-resize', '500', '500', tmp_path, '-o', output_path], + check=True, + capture_output=True + ) + + return f"https://monochrome.tf/editors-picks-images/{cover_uuid}.webp" + except Exception as e: + print(f"Error processing cover {cover_uuid}: {e}", file=sys.stderr) + return None + finally: + if os.path.exists(tmp_path): + os.remove(tmp_path) + + +def process_cover(cover_value): + if not cover_value: + return cover_value + if is_uuid_cover(cover_value): + return download_and_process_cover(cover_value) + return cover_value + + # ── Transformers ────────────────────────────────────────────────────────────── def transform_album(d): @@ -95,7 +155,7 @@ def transform_album(d): "name": d.get("artist", {}).get("name"), }, "releaseDate": d.get("releaseDate"), - "cover": d.get("cover"), + "cover": process_cover(d.get("cover")), "explicit": d.get("explicit"), "audioQuality": d.get("audioQuality"), "mediaMetadata": d.get("mediaMetadata"), @@ -107,7 +167,7 @@ def transform_artist(d): "type": "artist", "id": d.get("id"), "name": d.get("name"), - "picture": d.get("picture"), + "picture": process_cover(d.get("picture")), } @@ -124,7 +184,7 @@ def transform_track(d): "album": { "id": album.get("id"), "title": album.get("title"), - "cover": album.get("cover"), + "cover": process_cover(album.get("cover")), }, "duration": d.get("duration"), "explicit": d.get("explicit"), @@ -140,7 +200,7 @@ def transform_playlist(d): "type": "playlist", "id": d.get("uuid"), "title": d.get("title"), - "cover": cover, + "cover": process_cover(cover), "numberOfTracks": d.get("numberOfTracks", 0), } @@ -153,7 +213,7 @@ def transform_userplaylist(d): "type": "user-playlist", "id": d.get("uuid"), "name": d.get("title"), - "cover": cover, + "cover": process_cover(cover), "numberOfTracks": d.get("numberOfTracks", 0), "username": creator.get("name"), } @@ -198,6 +258,8 @@ def read_items(path): # ── Main ────────────────────────────────────────────────────────────────────── +clear_images_dir() + FETCHERS = { "album": (fetch_album, transform_album), "artist": (fetch_artist, transform_artist), @@ -212,7 +274,7 @@ picks = [] for item_type, item_id in items: if item_type not in FETCHERS: - print(f"Unknown type '{item_type}' for id {item_id!r} — skipping", file=sys.stderr) + print(f"Unknown type '{item_type}' for id {item_id!r} - skipping", file=sys.stderr) continue fetch_fn, transform_fn = FETCHERS[item_type] data = fetch_fn(item_id) diff --git a/js/taglib.worker.ts b/js/taglib.worker.ts index 71a0183..cd1644d 100644 --- a/js/taglib.worker.ts +++ b/js/taglib.worker.ts @@ -145,7 +145,7 @@ export async function addMetadataToAudio(message: _AddMetadataMessage): Promise< if (explicit !== undefined) { if (isMp4) { - // rtng is a byte item — must be set directly on the Mp4Tag + // rtng is a byte item - must be set directly on the Mp4Tag const mp4Tag = underlying.tag(); mp4Tag.setItem('rtng', Mp4Item.fromByte(explicit ? 1 : 0)); } else { From 8ad0228c1da6ce1082e15c06987dc0cdc309ecce Mon Sep 17 00:00:00 2001 From: edideaur Date: Sun, 5 Apr 2026 01:09:06 +0000 Subject: [PATCH 05/11] test --- editors-picks-input.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editors-picks-input.txt b/editors-picks-input.txt index 6d8a390..b365994 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -20,4 +20,5 @@ album:423471869 album:250986538 album:509761344 album:15621057 -album:103897783 \ No newline at end of file +album:103897783 +album:151728406 \ No newline at end of file From 31744df94ac5d1022635c473e0a2deaeb06dd60f Mon Sep 17 00:00:00 2001 From: edideaur Date: Sun, 5 Apr 2026 01:14:20 +0000 Subject: [PATCH 06/11] whoops --- editors-picks-input.txt | 3 +-- gen-editors-picks.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/editors-picks-input.txt b/editors-picks-input.txt index b365994..6d8a390 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -20,5 +20,4 @@ album:423471869 album:250986538 album:509761344 album:15621057 -album:103897783 -album:151728406 \ No newline at end of file +album:103897783 \ No newline at end of file diff --git a/gen-editors-picks.py b/gen-editors-picks.py index b2e03f3..4abef1c 100644 --- a/gen-editors-picks.py +++ b/gen-editors-picks.py @@ -17,7 +17,7 @@ IMAGES_DIR = "public/editors-picks-images" COUNTRY = "US" # Tidal internal token replace when expired -TIDAL_TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MjQ2NzQ2LCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.ksUE4yhQ39IG7oHWk8DyJ91dwIoDVWGzvTAnpeDJ5p-_Gp0F_yO858xDO11AINBaahQCq0jlbqWqIaTqCTOjqg" +TIDAL_TOKEN = "eyJraWQiOiJ2OU1GbFhqWSIsImFsZyI6IkVTMjU2In0.eyJ0eXBlIjoibzJfYWNjZXNzIiwic2NvcGUiOiIiLCJnVmVyIjowLCJzVmVyIjowLCJjaWQiOjEzNTU3LCJhdCI6IklOVEVSTkFMIiwiZXhwIjoxNzc1MzY0MTQwLCJpc3MiOiJodHRwczovL2F1dGgudGlkYWwuY29tL3YxIn0.6ui6itHVQ-OXPF0F9mbf5KcKz1fKYJNsa1vBAj60upXpcN-DQG8JPKBlqJN6RuBEH8yhwYj2wh4YJ-TOOuO8DA" TIDAL_HEADERS = { "accept": "*/*", From a812198a070db2c943446337fe78f727d1d60420 Mon Sep 17 00:00:00 2001 From: edideaur Date: Sun, 5 Apr 2026 01:20:21 +0000 Subject: [PATCH 07/11] why not --- editors-picks-input.txt | 3 ++- gen-editors-picks.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/editors-picks-input.txt b/editors-picks-input.txt index 6d8a390..b365994 100644 --- a/editors-picks-input.txt +++ b/editors-picks-input.txt @@ -20,4 +20,5 @@ album:423471869 album:250986538 album:509761344 album:15621057 -album:103897783 \ No newline at end of file +album:103897783 +album:151728406 \ No newline at end of file diff --git a/gen-editors-picks.py b/gen-editors-picks.py index 4abef1c..e6404e0 100644 --- a/gen-editors-picks.py +++ b/gen-editors-picks.py @@ -107,7 +107,7 @@ def uuid_to_path_segments(uuid): def download_and_process_cover(cover_uuid): - url = f"https://resources.tidal.com/images/{uuid_to_path_segments(cover_uuid)}/640x640.jpg" + url = f"https://resources.tidal.com/images/{uuid_to_path_segments(cover_uuid)}/320x320.jpg" with tempfile.NamedTemporaryFile(suffix='.jpg', delete=False) as tmp: tmp_path = tmp.name @@ -121,7 +121,7 @@ def download_and_process_cover(cover_uuid): output_path = os.path.join(IMAGES_DIR, f"{cover_uuid}.webp") subprocess.run( - ['cwebp', '-q', '50', '-resize', '500', '500', tmp_path, '-o', output_path], + ['cwebp', '-q', '50', tmp_path, '-o', output_path], check=True, capture_output=True ) From 57226892ab60d23ce7dfe8b8a824fa290c5ebe55 Mon Sep 17 00:00:00 2001 From: binimum Date: Sun, 5 Apr 2026 12:09:52 +0000 Subject: [PATCH 08/11] feat: seo --- index.html | 54 +++++++++++++++++++++++++++---- js/lyrics.js | 4 +-- public/assets/banner-twitter.jpg | Bin 0 -> 6408 bytes public/assets/banner.jpg | Bin 0 -> 5938 bytes public/robots.txt | 11 +++++++ public/sitemap.xml | 43 ++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 public/assets/banner-twitter.jpg create mode 100644 public/assets/banner.jpg create mode 100644 public/robots.txt create mode 100644 public/sitemap.xml diff --git a/index.html b/index.html index 8ba694e..440323d 100644 --- a/index.html +++ b/index.html @@ -3,24 +3,66 @@ - Monochrome Music + Monochrome + - + + + + + + + + + + + + + + + + + + + + + + - - - - diff --git a/js/lyrics.js b/js/lyrics.js index e59ffae..484c5c1 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -278,13 +278,13 @@ export class LyricsManager { // Load Kuroshiro from CDN if (!window.Kuroshiro) { - await this.loadScript('https://unpkg.com/kuroshiro@1.2.0/dist/kuroshiro.min.js'); + await this.loadScript('https://cdn.jsdelivr.net/npm/kuroshiro@1.2.0/dist/kuroshiro.min.js'); } // Load Kuromoji analyzer from CDN if (!window.KuromojiAnalyzer) { await this.loadScript( - 'https://unpkg.com/kuroshiro-analyzer-kuromoji@1.1.0/dist/kuroshiro-analyzer-kuromoji.min.js' + 'https://cdn.jsdelivr.net/npm/kuroshiro-analyzer-kuromoji@1.1.0/dist/kuroshiro-analyzer-kuromoji.min.js' ); } diff --git a/public/assets/banner-twitter.jpg b/public/assets/banner-twitter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..83a9ec3476be9acf195fc06ad3a866b57974512c GIT binary patch literal 6408 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 G)qerhxZ)20 literal 0 HcmV?d00001 diff --git a/public/assets/banner.jpg b/public/assets/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..95f786d79ff66631ab4de1fc62b1148c63c60907 GIT binary patch literal 5938 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 PP166N0sqGtYqb0q4OlYm literal 0 HcmV?d00001 diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..e81a557 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,11 @@ +# robots.txt for https://monochrome.tf/ + +User-agent: * +Allow: / + +# Avoid indexing internal endpoints and auth flows. +Disallow: /functions/ +Disallow: /api/ +Disallow: /auth/ + +Sitemap: https://monochrome.tf/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..dcfdecf --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,43 @@ + + + + https://monochrome.tf/ + daily + 1.0 + + + https://monochrome.tf/search + daily + 0.8 + + + https://monochrome.tf/library + daily + 0.8 + + + https://monochrome.tf/recent + daily + 0.7 + + + https://monochrome.tf/podcasts + daily + 0.7 + + + https://monochrome.tf/unreleased + daily + 0.7 + + + https://monochrome.tf/parties + weekly + 0.6 + + + https://monochrome.tf/donate + monthly + 0.5 + + From 52b62a2b1bad4b58acfa3fcb1fedeac71d31ac0a Mon Sep 17 00:00:00 2001 From: binimum <61615730+binimum@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:10:32 +0000 Subject: [PATCH 09/11] style: auto-fix linting issues --- index.html | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/index.html b/index.html index 440323d..ba9a12f 100644 --- a/index.html +++ b/index.html @@ -12,14 +12,20 @@ - + - + @@ -27,7 +33,10 @@ - + - + From 6f918e5c05ed509122677f932986c58dca8808a0 Mon Sep 17 00:00:00 2001 From: binimum Date: Sun, 5 Apr 2026 12:11:54 +0000 Subject: [PATCH 10/11] one more --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 440323d..4cbc6f4 100644 --- a/index.html +++ b/index.html @@ -58,6 +58,7 @@ + From cffd97ff69d5a0ef91da2c16145e29e6d76a9b45 Mon Sep 17 00:00:00 2001 From: binimum <61615730+binimum@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:13:01 +0000 Subject: [PATCH 11/11] style: auto-fix linting issues --- index.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index aa8e0ce..d6b1c6e 100644 --- a/index.html +++ b/index.html @@ -66,7 +66,10 @@ } - +